1 /** 2 3 This module defines facilities for efficient checking of integral operations 4 against overflow, casting with loss of precision, unexpected change of sign, 5 etc. The checking (and possibly correction) can be done at operation level, for 6 example $(LREF opChecked)$(D !"+"(x, y, overflow)) adds two integrals `x` and 7 `y` and sets `overflow` to `true` if an overflow occurred. The flag `overflow` 8 (a `bool` passed by reference) is not touched if the operation succeeded, so the 9 same flag can be reused for a sequence of operations and tested at the end. 10 11 Issuing individual checked operations is flexible and efficient but often 12 tedious. The $(LREF Checked) facility offers encapsulated integral wrappers that 13 do all checking internally and have configurable behavior upon erroneous 14 results. For example, `Checked!int` is a type that behaves like `int` but aborts 15 execution immediately whenever involved in an operation that produces the 16 arithmetically wrong result. The accompanying convenience function $(LREF 17 checked) uses type deduction to convert a value `x` of integral type `T` to 18 `Checked!T` by means of `checked(x)`. For example, $(D checked(1_000_000) * 19 10_000) aborts execution because the operation overflows. Also, $(D checked(-1) > 20 uint(0)) aborts execution (even though the built-in comparison $(D int(-1) > 21 uint(0)) is surprisingly true due to language's conversion rules modeled after 22 C). Thus, `Checked!int` is a virtually drop-in replacement for `int` useable in 23 debug builds, to be replaced by `int` in release mode if efficiency demands it. 24 25 `Checked` has customizable behavior with the help of a second type parameter, 26 `Hook`. Depending on what methods `Hook` defines, core operations on the 27 underlying integral may be verified for overflow or completely redefined. If 28 `Hook` defines no method at all and carries no state, there is no change in 29 behavior, i.e. $(D Checked!(int, void)) is a wrapper around `int` that adds no 30 customization at all. 31 32 This module provides a few predefined hooks (below) that add useful behavior to 33 `Checked`: 34 35 $(UL 36 $(LI $(LREF Abort) fails every incorrect operation with a message to $(REF 37 stderr, std, stdio) followed by a call to `assert(0)`. It is the default 38 second parameter, i.e. `Checked!short` is the same as $(D Checked!(short, 39 Abort)). 40 ) 41 $(LI $(LREF Warn) prints incorrect operations to $(REF stderr, std, stdio) but 42 otherwise preserves the built-in behavior. 43 ) 44 $(LI $(LREF ProperCompare) fixes the comparison operators `==`, `!=`, `<`, `<=`, `>`, 45 and `>=` to return correct results in all circumstances, at a slight cost in 46 efficiency. For example, $(D Checked!(uint, ProperCompare)(1) > -1) is `true`, 47 which is not the case for the built-in comparison. Also, comparing numbers for 48 equality with floating-point numbers only passes if the integral can be 49 converted to the floating-point number precisely, so as to preserve transitivity 50 of equality. 51 ) 52 $(LI $(LREF WithNaN) reserves a special "Not a Number" value akin to the homonym 53 value reserved for floating-point values. Once a $(D Checked!(X, WithNaN)) gets 54 this special value, it preserves and propagates it until reassigned. 55 ) 56 $(LI $(LREF Saturate) implements saturating arithmetic, i.e. $(D Checked!(int, 57 Saturate)) "stops" at `int.max` for all operations that would cause an `int` to 58 overflow toward infinity, and at `int.min` for all operations that would 59 correspondingly overflow toward negative infinity. 60 ) 61 $(LI $(LREF Throw), like $(D Abort), immediately fails on a bounds error, but it 62 throws a CheckFailure exception in place of `assert(0)`. 63 ) 64 ) 65 66 These policies may be used alone, e.g. $(D Checked!(uint, WithNaN)) defines a 67 `uint`-like type that reaches a stable NaN state for all erroneous operations. 68 They may also be "stacked" on top of each other, owing to the property that a 69 checked integral emulates an actual integral, which means another checked 70 integral can be built on top of it. Some combinations of interest include: 71 72 $(UL 73 $(LI $(D Checked!(Checked!int, ProperCompare)) defines an `int` with fixed 74 comparison operators that will fail with `assert(0)` upon overflow. (Recall that 75 `Abort` is the default policy.) The order in which policies are combined is 76 important because the outermost policy (`ProperCompare` in this case) has the 77 first crack at intercepting an operator. The converse combination $(D 78 Checked!(Checked!(int, ProperCompare))) is meaningless because `Abort` will 79 intercept comparison and will fail without giving `ProperCompare` a chance to 80 intervene. 81 ) 82 $(LI $(D Checked!(Checked!(int, ProperCompare), WithNaN)) defines an `int`-like 83 type that supports a NaN value. For values that are not NaN, comparison works 84 properly. Again the composition order is important; $(D Checked!(Checked!(int, 85 WithNaN), ProperCompare)) does not have good semantics because `ProperCompare` 86 intercepts comparisons before the numbers involved are tested for NaN. 87 ) 88 ) 89 90 The hook's members are looked up statically in a Design by Introspection manner 91 and are all optional. The table below illustrates the members that a hook type 92 may define and their influence over the behavior of the `Checked` type using it. 93 In the table, `hook` is an alias for `Hook` if the type `Hook` does not 94 introduce any state, or an object of type `Hook` otherwise. 95 96 $(TABLE , 97 $(TR $(TH `Hook` member) $(TH Semantics in $(D Checked!(T, Hook))) 98 ) 99 $(TR $(TD `defaultValue`) $(TD If defined, `Hook.defaultValue!T` is used as the 100 default initializer of the payload.) 101 ) 102 $(TR $(TD `min`) $(TD If defined, `Hook.min!T` is used as the minimum value of 103 the payload.) 104 ) 105 $(TR $(TD `max`) $(TD If defined, `Hook.max!T` is used as the maximum value of 106 the payload.) 107 ) 108 $(TR $(TD `hookOpCast`) $(TD If defined, `hook.hookOpCast!U(get)` is forwarded 109 to unconditionally when the payload is to be cast to type `U`.) 110 ) 111 $(TR $(TD `onBadCast`) $(TD If defined and `hookOpCast` is $(I not) defined, 112 `onBadCast!U(get)` is forwarded to when the payload is to be cast to type `U` 113 and the cast would lose information or force a change of sign.) 114 ) 115 $(TR $(TD `hookOpEquals`) $(TD If defined, $(D hook.hookOpEquals(get, rhs)) is 116 forwarded to unconditionally when the payload is compared for equality against 117 value `rhs` of integral, floating point, or Boolean type.) 118 ) 119 $(TR $(TD `hookOpCmp`) $(TD If defined, $(D hook.hookOpCmp(get, rhs)) is 120 forwarded to unconditionally when the payload is compared for ordering against 121 value `rhs` of integral, floating point, or Boolean type.) 122 ) 123 $(TR $(TD `hookOpUnary`) $(TD If defined, `hook.hookOpUnary!op(get)` (where `op` 124 is the operator symbol) is forwarded to for unary operators `-` and `~`. In 125 addition, for unary operators `++` and `--`, `hook.hookOpUnary!op(payload)` is 126 called, where `payload` is a reference to the value wrapped by `Checked` so the 127 hook can change it.) 128 ) 129 $(TR $(TD `hookOpBinary`) $(TD If defined, $(D hook.hookOpBinary!op(get, rhs)) 130 (where `op` is the operator symbol and `rhs` is the right-hand side operand) is 131 forwarded to unconditionally for binary operators `+`, `-`, `*`, `/`, `%`, 132 `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) 133 ) 134 $(TR $(TD `hookOpBinaryRight`) $(TD If defined, $(D 135 hook.hookOpBinaryRight!op(lhs, get)) (where `op` is the operator symbol and 136 `lhs` is the left-hand side operand) is forwarded to unconditionally for binary 137 operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`.) 138 ) 139 $(TR $(TD `onOverflow`) $(TD If defined, `hook.onOverflow!op(get)` is forwarded 140 to for unary operators that overflow but only if `hookOpUnary` is not defined. 141 Unary `~` does not overflow; unary `-` overflows only when the most negative 142 value of a signed type is negated, and the result of the hook call is returned. 143 When the increment or decrement operators overflow, the payload is assigned the 144 result of `hook.onOverflow!op(get)`. When a binary operator overflows, the 145 result of $(D hook.onOverflow!op(get, rhs)) is returned, but only if `Hook` does 146 not define `hookOpBinary`.) 147 ) 148 $(TR $(TD `hookOpOpAssign`) $(TD If defined, $(D hook.hookOpOpAssign!op(payload, 149 rhs)) (where `op` is the operator symbol and `rhs` is the right-hand side 150 operand) is forwarded to unconditionally for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, 151 `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.) 152 ) 153 $(TR $(TD `onLowerBound`) $(TD If defined, $(D hook.onLowerBound(value, bound)) 154 (where `value` is the value being assigned) is forwarded to when the result of 155 binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, 156 and `>>>=` is less than the minimum value representable by `T`.) 157 ) 158 $(TR $(TD `onUpperBound`) $(TD If defined, $(D hook.onUpperBound(value, bound)) 159 (where `value` is the value being assigned) is forwarded to when the result of 160 binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, 161 and `>>>=` is greater than the maximum value representable by `T`.) 162 ) 163 ) 164 165 */ 166 module std.experimental.checkedint; 167 import std.traits : isFloatingPoint, isIntegral, isNumeric, isUnsigned, Unqual; 168 169 /// 170 unittest 171 { 172 int[] concatAndAdd(int[] a, int[] b, int offset) 173 { 174 // Aborts on overflow on size computation 175 auto r = new int[(checked(a.length) + b.length).get]; 176 // Aborts on overflow on element computation 177 foreach (i; 0 .. a.length) 178 r[i] = (a[i] + checked(offset)).get; 179 foreach (i; 0 .. b.length) 180 r[i + a.length] = (b[i] + checked(offset)).get; 181 return r; 182 } 183 assert(concatAndAdd([1, 2, 3], [4, 5], -1) == [0, 1, 2, 3, 4]); 184 } 185 186 /** 187 Checked integral type wraps an integral `T` and customizes its behavior with the 188 help of a `Hook` type. The type wrapped must be one of the predefined integrals 189 (unqualified), or another instance of `Checked`. 190 */ 191 struct Checked(T, Hook = Abort) 192 if (isIntegral!T || is(T == Checked!(U, H), U, H)) 193 { 194 import std.algorithm.comparison : among; 195 import std.traits : hasMember; 196 import std.experimental.allocator.common : stateSize; 197 198 /** 199 The type of the integral subject to checking. 200 */ 201 alias Representation = T; 202 203 // state { 204 static if (hasMember!(Hook, "defaultValue")) 205 private T payload = Hook.defaultValue!T; 206 else 207 private T payload; 208 /** 209 `hook` is a member variable if it has state, or an alias for `Hook` 210 otherwise. 211 */ 212 static if (stateSize!Hook > 0) Hook hook; 213 else alias hook = Hook; 214 // } state 215 216 // get 217 /** 218 Returns a copy of the underlying value. 219 */ 220 auto get() inout { return payload; } 221 /// 222 unittest 223 { 224 auto x = checked(ubyte(42)); 225 static assert(is(typeof(x.get()) == ubyte)); 226 assert(x.get == 42); 227 const y = checked(ubyte(42)); 228 static assert(is(typeof(y.get()) == const ubyte)); 229 assert(y.get == 42); 230 } 231 232 /** 233 Defines the minimum and maximum. These values are hookable by defining 234 `Hook.min` and/or `Hook.max`. 235 */ 236 static if (hasMember!(Hook, "min")) 237 { 238 enum Checked!(T, Hook) min = Checked!(T, Hook)(Hook.min!T); 239 /// 240 unittest 241 { 242 assert(Checked!short.min == -32768); 243 assert(Checked!(short, WithNaN).min == -32767); 244 assert(Checked!(uint, WithNaN).max == uint.max - 1); 245 } 246 } 247 else 248 enum Checked!(T, Hook) min = Checked(T.min); 249 /// ditto 250 static if (hasMember!(Hook, "max")) 251 enum Checked!(T, Hook) max = Checked(Hook.max!T); 252 else 253 enum Checked!(T, Hook) max = Checked(T.max); 254 255 /** 256 Constructor taking a value properly convertible to the underlying type. `U` 257 may be either an integral that can be converted to `T` without a loss, or 258 another `Checked` instance whose representation may be in turn converted to 259 `T` without a loss. 260 */ 261 this(U)(U rhs) 262 if (valueConvertible!(U, T) || 263 !isIntegral!T && is(typeof(T(rhs))) || 264 is(U == Checked!(V, W), V, W) && 265 is(typeof(Checked!(T, Hook)(rhs.get)))) 266 { 267 static if (isIntegral!U) 268 payload = rhs; 269 else 270 payload = rhs.payload; 271 } 272 /// 273 unittest 274 { 275 auto a = checked(42L); 276 assert(a == 42); 277 auto b = Checked!long(4242); // convert 4242 to long 278 assert(b == 4242); 279 } 280 281 /** 282 Assignment operator. Has the same constraints as the constructor. 283 */ 284 void opAssign(U)(U rhs) if (is(typeof(Checked!(T, Hook)(rhs)))) 285 { 286 static if (isIntegral!U) 287 payload = rhs; 288 else 289 payload = rhs.payload; 290 } 291 /// 292 unittest 293 { 294 Checked!long a; 295 a = 42L; 296 assert(a == 42); 297 a = 4242; 298 assert(a == 4242); 299 } 300 301 // opCast 302 /** 303 Casting operator to integral, `bool`, or floating point type. If `Hook` 304 defines `hookOpCast`, the call immediately returns 305 `hook.hookOpCast!U(get)`. Otherwise, casting to `bool` yields $(D 306 get != 0) and casting to another integral that can represent all 307 values of `T` returns `get` promoted to `U`. 308 309 If a cast to a floating-point type is requested and `Hook` defines 310 `onBadCast`, the cast is verified by ensuring $(D get == cast(T) 311 U(get)). If that is not `true`, `hook.onBadCast!U(get)` is returned. 312 313 If a cast to an integral type is requested and `Hook` defines `onBadCast`, 314 the cast is verified by ensuring `get` and $(D cast(U) 315 get) are the same arithmetic number. (Note that `int(-1)` and 316 `uint(1)` are different values arithmetically although they have the same 317 bitwise representation and compare equal by language rules.) If the numbers 318 are not arithmetically equal, `hook.onBadCast!U(get)` is 319 returned. 320 321 */ 322 U opCast(U, this _)() 323 if (isIntegral!U || isFloatingPoint!U || is(U == bool)) 324 { 325 static if (hasMember!(Hook, "hookOpCast")) 326 { 327 return hook.hookOpCast!U(payload); 328 } 329 else static if (is(U == bool)) 330 { 331 return payload != 0; 332 } 333 else static if (valueConvertible!(T, U)) 334 { 335 return payload; 336 } 337 // may lose bits or precision 338 else static if (!hasMember!(Hook, "onBadCast")) 339 { 340 return cast(U) payload; 341 } 342 else 343 { 344 if (isUnsigned!T || !isUnsigned!U || 345 T.sizeof > U.sizeof || payload >= 0) 346 { 347 auto result = cast(U) payload; 348 // If signedness is different, we need additional checks 349 if (result == payload && 350 (!isUnsigned!T || isUnsigned!U || result >= 0)) 351 return result; 352 } 353 return hook.onBadCast!U(payload); 354 } 355 } 356 /// 357 unittest 358 { 359 assert(cast(uint) checked(42) == 42); 360 assert(cast(uint) checked!WithNaN(-42) == uint.max); 361 } 362 363 // opEquals 364 /** 365 Compares `this` against `rhs` for equality. If `Hook` defines 366 `hookOpEquals`, the function forwards to $(D 367 hook.hookOpEquals(get, rhs)). Otherwise, the result of the 368 built-in operation $(D get == rhs) is returned. 369 370 If `U` is also an instance of `Checked`, both hooks (left- and right-hand 371 side) are introspected for the method `hookOpEquals`. If both define it, 372 priority is given to the left-hand side. 373 374 */ 375 bool opEquals(U, this _)(U rhs) 376 if (isIntegral!U || isFloatingPoint!U || is(U == bool) || 377 is(U == Checked!(V, W), V, W) && is(typeof(this == rhs.payload))) 378 { 379 static if (is(U == Checked!(V, W), V, W)) 380 { 381 alias R = typeof(payload + rhs.payload); 382 static if (is(Hook == W)) 383 { 384 // Use the lhs hook if there 385 return this == rhs.payload; 386 } 387 else static if (valueConvertible!(T, R) && valueConvertible!(V, R)) 388 { 389 return payload == rhs.payload; 390 } 391 else static if (hasMember!(Hook, "hookOpEquals")) 392 { 393 return hook.hookOpEquals(payload, rhs.payload); 394 } 395 else static if (hasMember!(W, "hookOpEquals")) 396 { 397 return rhs.hook.hookOpEquals(rhs.payload, payload); 398 } 399 else 400 { 401 return payload == rhs.payload; 402 } 403 } 404 else static if (hasMember!(Hook, "hookOpEquals")) 405 return hook.hookOpEquals(payload, rhs); 406 else static if (isIntegral!U || isFloatingPoint!U || is(U == bool)) 407 return payload == rhs; 408 } 409 410 /// 411 static if (is(T == int) && is(Hook == void)) unittest 412 { 413 static struct MyHook 414 { 415 static bool thereWereErrors; 416 static bool hookOpEquals(L, R)(L lhs, R rhs) 417 { 418 if (lhs != rhs) return false; 419 static if (isUnsigned!L && !isUnsigned!R) 420 { 421 if (lhs > 0 && rhs < 0) thereWereErrors = true; 422 } 423 else static if (isUnsigned!R && !isUnsigned!L) 424 if (lhs < 0 && rhs > 0) thereWereErrors = true; 425 // Preserve built-in behavior. 426 return true; 427 } 428 } 429 auto a = checked!MyHook(-42); 430 assert(a == uint(-42)); 431 assert(MyHook.thereWereErrors); 432 MyHook.thereWereErrors = false; 433 assert(checked!MyHook(uint(-42)) == -42); 434 assert(MyHook.thereWereErrors); 435 static struct MyHook2 436 { 437 static bool hookOpEquals(L, R)(L lhs, R rhs) 438 { 439 return lhs == rhs; 440 } 441 } 442 MyHook.thereWereErrors = false; 443 assert(checked!MyHook2(uint(-42)) == a); 444 // Hook on left hand side takes precedence, so no errors 445 assert(!MyHook.thereWereErrors); 446 } 447 448 // opCmp 449 /** 450 451 Compares `this` against `rhs` for ordering. If `Hook` defines `hookOpCmp`, 452 the function forwards to $(D hook.hookOpCmp(get, rhs)). Otherwise, the 453 result of the built-in comparison operation is returned. 454 455 If `U` is also an instance of `Checked`, both hooks (left- and right-hand 456 side) are introspected for the method `hookOpCmp`. If both define it, 457 priority is given to the left-hand side. 458 459 */ 460 auto opCmp(U, this _)(const U rhs) //const pure @safe nothrow @nogc 461 if (isIntegral!U || isFloatingPoint!U || is(U == bool)) 462 { 463 static if (hasMember!(Hook, "hookOpCmp")) 464 { 465 return hook.hookOpCmp(payload, rhs); 466 } 467 else static if (valueConvertible!(T, U) || valueConvertible!(U, T)) 468 { 469 return payload < rhs ? -1 : payload > rhs; 470 } 471 else static if (isFloatingPoint!U) 472 { 473 U lhs = payload; 474 return lhs < rhs ? U(-1.0) 475 : lhs > rhs ? U(1.0) 476 : lhs == rhs ? U(0.0) : U.init; 477 } 478 else 479 { 480 return payload < rhs ? -1 : payload > rhs; 481 } 482 } 483 484 /// ditto 485 auto opCmp(U, Hook1, this _)(Checked!(U, Hook1) rhs) 486 { 487 alias R = typeof(payload + rhs.payload); 488 static if (valueConvertible!(T, R) && valueConvertible!(U, R)) 489 { 490 return payload < rhs.payload ? -1 : payload > rhs.payload; 491 } 492 else static if (is(Hook == Hook1)) 493 { 494 // Use the lhs hook 495 return this.opCmp(rhs.payload); 496 } 497 else static if (hasMember!(Hook, "hookOpCmp")) 498 { 499 return hook.hookOpCmp(get, rhs.get); 500 } 501 else static if (hasMember!(Hook1, "hookOpCmp")) 502 { 503 return -rhs.hook.hookOpCmp(rhs.payload, get); 504 } 505 else 506 { 507 return payload < rhs.payload ? -1 : payload > rhs.payload; 508 } 509 } 510 511 /// 512 static if (is(T == int) && is(Hook == void)) unittest 513 { 514 static struct MyHook 515 { 516 static bool thereWereErrors; 517 static int hookOpCmp(L, R)(L lhs, R rhs) 518 { 519 static if (isUnsigned!L && !isUnsigned!R) 520 { 521 if (rhs < 0 && rhs >= lhs) 522 thereWereErrors = true; 523 } 524 else static if (isUnsigned!R && !isUnsigned!L) 525 { 526 if (lhs < 0 && lhs >= rhs) 527 thereWereErrors = true; 528 } 529 // Preserve built-in behavior. 530 return lhs < rhs ? -1 : lhs > rhs; 531 } 532 } 533 auto a = checked!MyHook(-42); 534 assert(a > uint(42)); 535 assert(MyHook.thereWereErrors); 536 static struct MyHook2 537 { 538 static int hookOpCmp(L, R)(L lhs, R rhs) 539 { 540 // Default behavior 541 return lhs < rhs ? -1 : lhs > rhs; 542 } 543 } 544 MyHook.thereWereErrors = false; 545 assert(Checked!(uint, MyHook2)(uint(-42)) <= a); 546 //assert(Checked!(uint, MyHook2)(uint(-42)) >= a); 547 // Hook on left hand side takes precedence, so no errors 548 assert(!MyHook.thereWereErrors); 549 assert(a <= Checked!(uint, MyHook2)(uint(-42))); 550 assert(MyHook.thereWereErrors); 551 } 552 553 // For coverage 554 static if (is(T == int) && is(Hook == void)) unittest 555 { 556 assert(checked(42) <= checked!void(42)); 557 assert(checked!void(42) <= checked(42u)); 558 assert(checked!void(42) <= checked!(void*)(42u)); 559 } 560 561 // opUnary 562 /** 563 564 Defines unary operators `+`, `-`, `~`, `++`, and `--`. Unary `+` is not 565 overridable and always has built-in behavior (returns `this`). For the 566 others, if `Hook` defines `hookOpUnary`, `opUnary` forwards to $(D 567 Checked!(typeof(hook.hookOpUnary!op(get)), 568 Hook)(hook.hookOpUnary!op(get))). 569 570 If `Hook` does not define `hookOpUnary` but defines `onOverflow`, `opUnary` 571 forwards to `hook.onOverflow!op(get)` in case an overflow occurs. 572 For `++` and `--`, the payload is assigned from the result of the call to 573 `onOverflow`. 574 575 Note that unary `-` is considered to overflow if `T` is a signed integral of 576 32 or 64 bits and is equal to the most negative value. This is because that 577 value has no positive negation. 578 579 */ 580 auto opUnary(string op, this _)() 581 if (op == "+" || op == "-" || op == "~") 582 { 583 static if (op == "+") 584 return Checked(this); // "+" is not hookable 585 else static if (hasMember!(Hook, "hookOpUnary")) 586 { 587 auto r = hook.hookOpUnary!op(payload); 588 return Checked!(typeof(r), Hook)(r); 589 } 590 else static if (op == "-" && isIntegral!T && T.sizeof >= 4 && 591 !isUnsigned!T && hasMember!(Hook, "onOverflow")) 592 { 593 import core.checkedint; 594 static assert(is(typeof(-payload) == typeof(payload))); 595 bool overflow; 596 auto r = negs(payload, overflow); 597 if (overflow) r = hook.onOverflow!op(payload); 598 return Checked(r); 599 } 600 else 601 return Checked(mixin(op ~ "payload")); 602 } 603 604 /// ditto 605 ref Checked opUnary(string op)() return 606 if (op == "++" || op == "--") 607 { 608 static if (hasMember!(Hook, "hookOpUnary")) 609 hook.hookOpUnary!op(payload); 610 else static if (hasMember!(Hook, "onOverflow")) 611 { 612 static if (op == "++") 613 { 614 if (payload == max.payload) 615 payload = hook.onOverflow!"++"(payload); 616 else 617 ++payload; 618 } 619 else 620 { 621 if (payload == min.payload) 622 payload = hook.onOverflow!"--"(payload); 623 else 624 --payload; 625 } 626 } 627 else 628 mixin(op ~ "payload;"); 629 return this; 630 } 631 632 /// 633 static if (is(T == int) && is(Hook == void)) unittest 634 { 635 static struct MyHook 636 { 637 static bool thereWereErrors; 638 static L hookOpUnary(string x, L)(L lhs) 639 { 640 if (x == "-" && lhs == -lhs) thereWereErrors = true; 641 return -lhs; 642 } 643 } 644 auto a = checked!MyHook(long.min); 645 assert(a == -a); 646 assert(MyHook.thereWereErrors); 647 auto b = checked!void(42); 648 assert(++b == 43); 649 } 650 651 // opBinary 652 /** 653 654 Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, 655 and `>>>`. If `Hook` defines `hookOpBinary`, `opBinary` forwards to $(D 656 Checked!(typeof(hook.hookOpBinary!op(get, rhs)), 657 Hook)(hook.hookOpBinary!op(get, rhs))). 658 659 If `Hook` does not define `hookOpBinary` but defines `onOverflow`, 660 `opBinary` forwards to `hook.onOverflow!op(get, rhs)` in case an 661 overflow occurs. 662 663 If two `Checked` instances are involved in a binary operation and both 664 define `hookOpBinary`, the left-hand side hook has priority. If both define 665 `onOverflow`, a compile-time error occurs. 666 667 */ 668 auto opBinary(string op, Rhs)(const Rhs rhs) 669 if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) 670 { 671 return opBinaryImpl!(op, Rhs, typeof(this))(rhs); 672 } 673 674 /// ditto 675 auto opBinary(string op, Rhs)(const Rhs rhs) const 676 if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) 677 { 678 return opBinaryImpl!(op, Rhs, typeof(this))(rhs); 679 } 680 681 private auto opBinaryImpl(string op, Rhs, this _)(const Rhs rhs) 682 { 683 alias R = typeof(mixin("payload" ~ op ~ "rhs")); 684 static assert(is(typeof(mixin("payload" ~ op ~ "rhs")) == R)); 685 static if (isIntegral!R) alias Result = Checked!(R, Hook); 686 else alias Result = R; 687 688 static if (hasMember!(Hook, "hookOpBinary")) 689 { 690 auto r = hook.hookOpBinary!op(payload, rhs); 691 return Checked!(typeof(r), Hook)(r); 692 } 693 else static if (is(Rhs == bool)) 694 { 695 return mixin("this" ~ op ~ "ubyte(rhs)"); 696 } 697 else static if (isFloatingPoint!Rhs) 698 { 699 return mixin("payload" ~ op ~ "rhs"); 700 } 701 else static if (hasMember!(Hook, "onOverflow")) 702 { 703 bool overflow; 704 auto r = opChecked!op(payload, rhs, overflow); 705 if (overflow) r = hook.onOverflow!op(payload, rhs); 706 return Result(r); 707 } 708 else 709 { 710 // Default is built-in behavior 711 return Result(mixin("payload" ~ op ~ "rhs")); 712 } 713 } 714 715 /// ditto 716 auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) 717 { 718 return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); 719 } 720 721 /// ditto 722 auto opBinary(string op, U, Hook1)(Checked!(U, Hook1) rhs) const 723 { 724 return opBinaryImpl2!(op, U, Hook1, typeof(this))(rhs); 725 } 726 727 private 728 auto opBinaryImpl2(string op, U, Hook1, this _)(Checked!(U, Hook1) rhs) 729 { 730 alias R = typeof(get + rhs.payload); 731 static if (valueConvertible!(T, R) && valueConvertible!(U, R) || 732 is(Hook == Hook1)) 733 { 734 // Delegate to lhs 735 return mixin("this" ~ op ~ "rhs.payload"); 736 } 737 else static if (hasMember!(Hook, "hookOpBinary")) 738 { 739 return hook.hookOpBinary!op(payload, rhs); 740 } 741 else static if (hasMember!(Hook1, "hookOpBinary")) 742 { 743 // Delegate to rhs 744 return mixin("this.payload" ~ op ~ "rhs"); 745 } 746 else static if (hasMember!(Hook, "onOverflow") && 747 !hasMember!(Hook1, "onOverflow")) 748 { 749 // Delegate to lhs 750 return mixin("this" ~ op ~ "rhs.payload"); 751 } 752 else static if (hasMember!(Hook1, "onOverflow") && 753 !hasMember!(Hook, "onOverflow")) 754 { 755 // Delegate to rhs 756 return mixin("this.payload" ~ op ~ "rhs"); 757 } 758 else 759 { 760 static assert(0, "Conflict between lhs and rhs hooks," ~ 761 " use .get on one side to disambiguate."); 762 } 763 } 764 765 static if (is(T == int) && is(Hook == void)) unittest 766 { 767 const a = checked(42); 768 assert(a + 1 == 43); 769 assert(a + checked(uint(42)) == 84); 770 assert(checked(42) + checked!void(42u) == 84); 771 assert(checked!void(42) + checked(42u) == 84); 772 773 static struct MyHook 774 { 775 static uint tally; 776 static auto hookOpBinary(string x, L, R)(L lhs, R rhs) 777 { 778 ++tally; 779 return mixin("lhs" ~ x ~ "rhs"); 780 } 781 } 782 assert(checked!MyHook(42) + checked(42u) == 84); 783 assert(checked!void(42) + checked!MyHook(42u) == 84); 784 assert(MyHook.tally == 2); 785 } 786 787 // opBinaryRight 788 /** 789 790 Defines binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, `^`, `<<`, 791 `>>`, and `>>>` for the case when a built-in numeric or Boolean type is on 792 the left-hand side, and a `Checked` instance is on the right-hand side. 793 794 */ 795 auto opBinaryRight(string op, Lhs)(const Lhs lhs) 796 if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) 797 { 798 return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); 799 } 800 801 /// ditto 802 auto opBinaryRight(string op, Lhs)(const Lhs lhs) const 803 if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) 804 { 805 return opBinaryRightImpl!(op, Lhs, typeof(this))(lhs); 806 } 807 808 private auto opBinaryRightImpl(string op, Lhs, this _)(const Lhs lhs) 809 { 810 static if (hasMember!(Hook, "hookOpBinaryRight")) 811 { 812 auto r = hook.hookOpBinaryRight!op(lhs, payload); 813 return Checked!(typeof(r), Hook)(r); 814 } 815 else static if (hasMember!(Hook, "hookOpBinary")) 816 { 817 auto r = hook.hookOpBinary!op(lhs, payload); 818 return Checked!(typeof(r), Hook)(r); 819 } 820 else static if (is(Lhs == bool)) 821 { 822 return mixin("ubyte(lhs)" ~ op ~ "this"); 823 } 824 else static if (isFloatingPoint!Lhs) 825 { 826 return mixin("lhs" ~ op ~ "payload"); 827 } 828 else static if (hasMember!(Hook, "onOverflow")) 829 { 830 bool overflow; 831 auto r = opChecked!op(lhs, T(payload), overflow); 832 if (overflow) r = hook.onOverflow!op(42); 833 return Checked!(typeof(r), Hook)(r); 834 } 835 else 836 { 837 // Default is built-in behavior 838 auto r = mixin("lhs" ~ op ~ "T(payload)"); 839 return Checked!(typeof(r), Hook)(r); 840 } 841 } 842 843 static if (is(T == int) && is(Hook == void)) unittest 844 { 845 assert(1 + checked(1) == 2); 846 static uint tally; 847 static struct MyHook 848 { 849 static auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) 850 { 851 ++tally; 852 return mixin("lhs" ~ x ~ "rhs"); 853 } 854 } 855 assert(1 + checked!MyHook(1) == 2); 856 assert(tally == 1); 857 858 immutable x1 = checked(1); 859 assert(1 + x1 == 2); 860 immutable x2 = checked!MyHook(1); 861 assert(1 + x2 == 2); 862 assert(tally == 2); 863 } 864 865 // opOpAssign 866 /** 867 868 Defines operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, 869 `<<=`, `>>=`, and `>>>=`. 870 871 If `Hook` defines `hookOpOpAssign`, `opOpAssign` forwards to 872 `hook.hookOpOpAssign!op(payload, rhs)`, where `payload` is a reference to 873 the internally held data so the hook can change it. 874 875 Otherwise, the operator first evaluates $(D auto result = 876 opBinary!op(payload, rhs).payload), which is subject to the hooks in 877 `opBinary`. Then, if `result` is less than $(D Checked!(T, Hook).min) and if 878 `Hook` defines `onLowerBound`, the payload is assigned from $(D 879 hook.onLowerBound(result, min)). If `result` is greater than $(D Checked!(T, 880 Hook).max) and if `Hook` defines `onUpperBound`, the payload is assigned 881 from $(D hook.onUpperBound(result, min)). 882 883 In all other cases, the built-in behavior is carried out. 884 885 Params: 886 op = The operator involved (without the `"="`, e.g. `"+"` for `"+="` etc) 887 rhs = The right-hand side of the operator (left-hand side is `this`) 888 889 Returns: A reference to `this`. 890 */ 891 ref Checked opOpAssign(string op, Rhs)(const Rhs rhs) return 892 if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) 893 { 894 static assert(is(typeof(mixin("payload" ~ op ~ "=rhs")) == T)); 895 896 static if (hasMember!(Hook, "hookOpOpAssign")) 897 { 898 hook.hookOpOpAssign!op(payload, rhs); 899 } 900 else 901 { 902 alias R = typeof(get + rhs); 903 auto r = opBinary!op(rhs).get; 904 import std.conv : unsigned; 905 906 static if (ProperCompare.hookOpCmp(R.min, min.get) < 0 && 907 hasMember!(Hook, "onLowerBound")) 908 { 909 if (ProperCompare.hookOpCmp(r, min.get) < 0) 910 { 911 // Example: Checked!uint(1) += int(-3) 912 payload = hook.onLowerBound(r, min.get); 913 return this; 914 } 915 } 916 static if (ProperCompare.hookOpCmp(max.get, R.max) < 0 && 917 hasMember!(Hook, "onUpperBound")) 918 { 919 if (ProperCompare.hookOpCmp(r, max.get) > 0) 920 { 921 // Example: Checked!uint(1) += long(uint.max) 922 payload = hook.onUpperBound(r, max.get); 923 return this; 924 } 925 } 926 payload = cast(T) r; 927 } 928 return this; 929 } 930 931 /// 932 static if (is(T == int) && is(Hook == void)) unittest 933 { 934 static struct MyHook 935 { 936 static bool thereWereErrors; 937 static T onLowerBound(Rhs, T)(Rhs rhs, T bound) 938 { 939 thereWereErrors = true; 940 return bound; 941 } 942 static T onUpperBound(Rhs, T)(Rhs rhs, T bound) 943 { 944 thereWereErrors = true; 945 return bound; 946 } 947 } 948 auto x = checked!MyHook(byte.min); 949 x -= 1; 950 assert(MyHook.thereWereErrors); 951 MyHook.thereWereErrors = false; 952 x = byte.max; 953 x += 1; 954 assert(MyHook.thereWereErrors); 955 } 956 } 957 958 /** 959 960 Convenience function that turns an integral into the corresponding `Checked` 961 instance by using template argument deduction. The hook type may be specified 962 (by default `Abort`). 963 964 */ 965 Checked!(T, Hook) checked(Hook = Abort, T)(const T value) 966 if (is(typeof(Checked!(T, Hook)(value)))) 967 { 968 return Checked!(T, Hook)(value); 969 } 970 971 /// 972 unittest 973 { 974 static assert(is(typeof(checked(42)) == Checked!int)); 975 assert(checked(42) == Checked!int(42)); 976 static assert(is(typeof(checked!WithNaN(42)) == Checked!(int, WithNaN))); 977 assert(checked!WithNaN(42) == Checked!(int, WithNaN)(42)); 978 } 979 980 // get 981 unittest 982 { 983 void test(T)() 984 { 985 assert(Checked!(T, void)(ubyte(22)).get == 22); 986 } 987 test!ubyte; 988 test!(const ubyte); 989 test!(immutable ubyte); 990 } 991 992 // Abort 993 /** 994 995 Force all integral errors to fail by printing an error message to `stderr` and 996 then abort the program. `Abort` is the default second argument for `Checked`. 997 998 */ 999 struct Abort 1000 { 1001 static: 1002 /** 1003 1004 Called automatically upon a bad cast (one that loses precision or attempts 1005 to convert a negative value to an unsigned type). The source type is `Src` 1006 and the destination type is `Dst`. 1007 1008 Params: 1009 src = The source of the cast 1010 1011 Returns: Nominally the result is the desired value of the cast operation, 1012 which will be forwarded as the result of the cast. For `Abort`, the 1013 function never returns because it aborts the program. 1014 1015 */ 1016 Dst onBadCast(Dst, Src)(Src src) 1017 { 1018 Warn.onBadCast!Dst(src); 1019 assert(0); 1020 } 1021 1022 /** 1023 1024 Called automatically upon a bounds error. 1025 1026 Params: 1027 rhs = The right-hand side value in the assignment, after the operator has 1028 been evaluated 1029 bound = The value of the bound being violated 1030 1031 Returns: Nominally the result is the desired value of the operator, which 1032 will be forwarded as result. For `Abort`, the function never returns because 1033 it aborts the program. 1034 1035 */ 1036 T onLowerBound(Rhs, T)(Rhs rhs, T bound) 1037 { 1038 Warn.onLowerBound(rhs, bound); 1039 assert(0); 1040 } 1041 /// ditto 1042 T onUpperBound(Rhs, T)(Rhs rhs, T bound) 1043 { 1044 Warn.onUpperBound(rhs, bound); 1045 assert(0); 1046 } 1047 1048 /** 1049 1050 Called automatically upon a comparison for equality. In case of a erroneous 1051 comparison (one that would make a signed negative value appear equal to an 1052 unsigned positive value), this hook issues `assert(0)` which terminates the 1053 application. 1054 1055 Params: 1056 lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of 1057 the operator is `Checked!int` 1058 rhs = The right-hand side type involved in the operator 1059 1060 Returns: Upon a correct comparison, returns the result of the comparison. 1061 Otherwise, the function terminates the application so it never returns. 1062 1063 */ 1064 static bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) 1065 { 1066 bool error; 1067 auto result = opChecked!"=="(lhs, rhs, error); 1068 if (error) 1069 { 1070 Warn.hookOpEquals(lhs, rhs); 1071 assert(0); 1072 } 1073 return result; 1074 } 1075 1076 /** 1077 1078 Called automatically upon a comparison for ordering using one of the 1079 operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. 1080 it would make a signed negative value appear greater than or equal to an 1081 unsigned positive value), then application is terminated with `assert(0)`. 1082 Otherwise, the three-state result is returned (positive if $(D lhs > rhs), 1083 negative if $(D lhs < rhs), `0` otherwise). 1084 1085 Params: 1086 lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of 1087 the operator is `Checked!int` 1088 rhs = The right-hand side type involved in the operator 1089 1090 Returns: For correct comparisons, returns a positive integer if $(D lhs > 1091 rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. Upon 1092 a mistaken comparison such as $(D int(-1) < uint(0)), the function never 1093 returns because it aborts the program. 1094 1095 */ 1096 int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) 1097 { 1098 bool error; 1099 auto result = opChecked!"cmp"(lhs, rhs, error); 1100 if (error) 1101 { 1102 Warn.hookOpCmp(lhs, rhs); 1103 assert(0); 1104 } 1105 return result; 1106 } 1107 1108 /** 1109 1110 Called automatically upon an overflow during a unary or binary operation. 1111 1112 Params: 1113 x = The operator, e.g. `-` 1114 lhs = The left-hand side (or sole) argument 1115 rhs = The right-hand side type involved in the operator 1116 1117 Returns: Nominally the result is the desired value of the operator, which 1118 will be forwarded as result. For `Abort`, the function never returns because 1119 it aborts the program. 1120 1121 */ 1122 typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) 1123 { 1124 Warn.onOverflow!x(lhs); 1125 assert(0); 1126 } 1127 /// ditto 1128 typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) 1129 { 1130 Warn.onOverflow!x(lhs, rhs); 1131 assert(0); 1132 } 1133 } 1134 1135 unittest 1136 { 1137 void test(T)() 1138 { 1139 Checked!(int, Abort) x; 1140 x = 42; 1141 auto x1 = cast(T) x; 1142 assert(x1 == 42); 1143 //x1 += long(int.max); 1144 } 1145 test!short; 1146 test!(const short); 1147 test!(immutable short); 1148 } 1149 1150 1151 // Throw 1152 /** 1153 1154 Force all integral errors to fail by throwing an exception of type 1155 `Throw.CheckFailure`. The message coming with the error is similar to the one 1156 printed by `Warn`. 1157 1158 */ 1159 struct Throw 1160 { 1161 /** 1162 Exception type thrown upon any failure. 1163 */ 1164 static class CheckFailure : Exception 1165 { 1166 this(T...)(string f, T vals) 1167 { 1168 import std.format; 1169 super(format(f, vals)); 1170 } 1171 } 1172 1173 /** 1174 1175 Called automatically upon a bad cast (one that loses precision or attempts 1176 to convert a negative value to an unsigned type). The source type is `Src` 1177 and the destination type is `Dst`. 1178 1179 Params: 1180 src = The source of the cast 1181 1182 Returns: Nominally the result is the desired value of the cast operation, 1183 which will be forwarded as the result of the cast. For `Throw`, the 1184 function never returns because it throws an exception. 1185 1186 */ 1187 static Dst onBadCast(Dst, Src)(Src src) 1188 { 1189 throw new CheckFailure("Erroneous cast: cast(%s) %s(%s)", 1190 Dst.stringof, Src.stringof, src); 1191 } 1192 1193 /** 1194 1195 Called automatically upon a bounds error. 1196 1197 Params: 1198 rhs = The right-hand side value in the assignment, after the operator has 1199 been evaluated 1200 bound = The value of the bound being violated 1201 1202 Returns: Nominally the result is the desired value of the operator, which 1203 will be forwarded as result. For `Throw`, the function never returns because 1204 it throws. 1205 1206 */ 1207 static T onLowerBound(Rhs, T)(Rhs rhs, T bound) 1208 { 1209 throw new CheckFailure("Lower bound error: %s(%s) < %s(%s)", 1210 Rhs.stringof, rhs, T.stringof, bound); 1211 } 1212 /// ditto 1213 static T onUpperBound(Rhs, T)(Rhs rhs, T bound) 1214 { 1215 throw new CheckFailure("Upper bound error: %s(%s) > %s(%s)", 1216 Rhs.stringof, rhs, T.stringof, bound); 1217 } 1218 1219 /** 1220 1221 Called automatically upon a comparison for equality. Throws upon an 1222 erroneous comparison (one that would make a signed negative value appear 1223 equal to an unsigned positive value). 1224 1225 Params: 1226 lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of 1227 the operator is `Checked!int` 1228 rhs = The right-hand side type involved in the operator 1229 1230 Returns: The result of the comparison. 1231 1232 Throws: `CheckFailure` if the comparison is mathematically erroneous. 1233 1234 */ 1235 static bool hookOpEquals(L, R)(L lhs, R rhs) 1236 { 1237 bool error; 1238 auto result = opChecked!"=="(lhs, rhs, error); 1239 if (error) 1240 { 1241 throw new CheckFailure("Erroneous comparison: %s(%s) == %s(%s)", 1242 L.stringof, lhs, R.stringof, rhs); 1243 } 1244 return result; 1245 } 1246 1247 /** 1248 1249 Called automatically upon a comparison for ordering using one of the 1250 operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. 1251 it would make a signed negative value appear greater than or equal to an 1252 unsigned positive value), throws a `Throw.CheckFailure` exception. 1253 Otherwise, the three-state result is returned (positive if $(D lhs > rhs), 1254 negative if $(D lhs < rhs), `0` otherwise). 1255 1256 Params: 1257 lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of 1258 the operator is `Checked!int` 1259 rhs = The right-hand side type involved in the operator 1260 1261 Returns: For correct comparisons, returns a positive integer if $(D lhs > 1262 rhs), a negative integer if $(D lhs < rhs), `0` if the two are equal. 1263 1264 Throws: Upon a mistaken comparison such as $(D int(-1) < uint(0)), the 1265 function never returns because it throws a `Throw.CheckedFailure` exception. 1266 1267 */ 1268 static int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) 1269 { 1270 bool error; 1271 auto result = opChecked!"cmp"(lhs, rhs, error); 1272 if (error) 1273 { 1274 throw new CheckFailure("Erroneous ordering comparison: %s(%s) and %s(%s)", 1275 Lhs.stringof, lhs, Rhs.stringof, rhs); 1276 } 1277 return result; 1278 } 1279 1280 /** 1281 1282 Called automatically upon an overflow during a unary or binary operation. 1283 1284 Params: 1285 x = The operator, e.g. `-` 1286 lhs = The left-hand side (or sole) argument 1287 rhs = The right-hand side type involved in the operator 1288 1289 Returns: Nominally the result is the desired value of the operator, which 1290 will be forwarded as result. For `Throw`, the function never returns because 1291 it throws an exception. 1292 1293 */ 1294 static typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) 1295 { 1296 throw new CheckFailure("Overflow on unary operator: %s%s(%s)", 1297 x, Lhs.stringof, lhs); 1298 } 1299 /// ditto 1300 static typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) 1301 { 1302 throw new CheckFailure("Overflow on binary operator: %s(%s) %s %s(%s)", 1303 Lhs.stringof, lhs, x, Rhs.stringof, rhs); 1304 } 1305 } 1306 1307 /// 1308 unittest 1309 { 1310 void test(T)() 1311 { 1312 Checked!(int, Throw) x; 1313 x = 42; 1314 auto x1 = cast(T) x; 1315 assert(x1 == 42); 1316 x = T.max + 1; 1317 import std.exception; 1318 assertThrown(cast(T) x); 1319 x = x.max; 1320 assertThrown(x += 42); 1321 assertThrown(x += 42L); 1322 x = x.min; 1323 assertThrown(-x); 1324 assertThrown(x -= 42); 1325 assertThrown(x -= 42L); 1326 x = -1; 1327 assertNotThrown(x == -1); 1328 assertThrown(x == uint(-1)); 1329 assertNotThrown(x <= -1); 1330 assertThrown(x <= uint(-1)); 1331 } 1332 test!short; 1333 test!(const short); 1334 test!(immutable short); 1335 } 1336 1337 // Warn 1338 /** 1339 Hook that prints to `stderr` a trace of all integral errors, without affecting 1340 default behavior. 1341 */ 1342 struct Warn 1343 { 1344 import std.stdio : stderr; 1345 static: 1346 /** 1347 1348 Called automatically upon a bad cast from `src` to type `Dst` (one that 1349 loses precision or attempts to convert a negative value to an unsigned 1350 type). 1351 1352 Params: 1353 src = The source of the cast 1354 Dst = The target type of the cast 1355 1356 Returns: `cast(Dst) src` 1357 1358 */ 1359 Dst onBadCast(Dst, Src)(Src src) 1360 { 1361 stderr.writefln("Erroneous cast: cast(%s) %s(%s)", 1362 Dst.stringof, Src.stringof, src); 1363 return cast(Dst) src; 1364 } 1365 1366 /** 1367 1368 Called automatically upon a bad `opOpAssign` call (one that loses precision 1369 or attempts to convert a negative value to an unsigned type). 1370 1371 Params: 1372 rhs = The right-hand side value in the assignment, after the operator has 1373 been evaluated 1374 bound = The bound being violated 1375 1376 Returns: `cast(Lhs) rhs` 1377 */ 1378 Lhs onLowerBound(Rhs, T)(Rhs rhs, T bound) 1379 { 1380 stderr.writefln("Lower bound error: %s(%s) < %s(%s)", 1381 Rhs.stringof, rhs, T.stringof, bound); 1382 return cast(T) rhs; 1383 } 1384 /// ditto 1385 T onUpperBound(Rhs, T)(Rhs rhs, T bound) 1386 { 1387 stderr.writefln("Upper bound error: %s(%s) > %s(%s)", 1388 Rhs.stringof, rhs, T.stringof, bound); 1389 return cast(T) rhs; 1390 } 1391 1392 /** 1393 1394 Called automatically upon a comparison for equality. In case of an Erroneous 1395 comparison (one that would make a signed negative value appear equal to an 1396 unsigned positive value), writes a warning message to `stderr` as a side 1397 effect. 1398 1399 Params: 1400 lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of 1401 the operator is `Checked!int` 1402 rhs = The right-hand side type involved in the operator 1403 1404 Returns: In all cases the function returns the built-in result of $(D lhs == 1405 rhs). 1406 1407 */ 1408 bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) 1409 { 1410 bool error; 1411 auto result = opChecked!"=="(lhs, rhs, error); 1412 if (error) 1413 { 1414 stderr.writefln("Erroneous comparison: %s(%s) == %s(%s)", 1415 Lhs.stringof, lhs, Rhs.stringof, rhs); 1416 return lhs == rhs; 1417 } 1418 return result; 1419 } 1420 1421 /// 1422 unittest 1423 { 1424 auto x = checked!Warn(-42); 1425 // Passes 1426 assert(x == -42); 1427 // Passes but prints a warning 1428 // assert(x == uint(-42)); 1429 } 1430 1431 /** 1432 1433 Called automatically upon a comparison for ordering using one of the 1434 operators `<`, `<=`, `>`, or `>=`. In case the comparison is erroneous (i.e. 1435 it would make a signed negative value appear greater than or equal to an 1436 unsigned positive value), then a warning message is printed to `stderr`. 1437 1438 Params: 1439 lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of 1440 the operator is `Checked!int` 1441 rhs = The right-hand side type involved in the operator 1442 1443 Returns: In all cases, returns $(D lhs < rhs ? -1 : lhs > rhs). The result 1444 is not autocorrected in case of an erroneous comparison. 1445 1446 */ 1447 int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) 1448 { 1449 bool error; 1450 auto result = opChecked!"cmp"(lhs, rhs, error); 1451 if (error) 1452 { 1453 stderr.writefln("Erroneous ordering comparison: %s(%s) and %s(%s)", 1454 Lhs.stringof, lhs, Rhs.stringof, rhs); 1455 return lhs < rhs ? -1 : lhs > rhs; 1456 } 1457 return result; 1458 } 1459 1460 /// 1461 unittest 1462 { 1463 auto x = checked!Warn(-42); 1464 // Passes 1465 assert(x <= -42); 1466 // Passes but prints a warning 1467 // assert(x <= uint(-42)); 1468 } 1469 1470 /** 1471 1472 Called automatically upon an overflow during a unary or binary operation. 1473 1474 Params: 1475 x = The operator involved 1476 Lhs = The first argument of `Checked`, e.g. `int` if the left-hand side of 1477 the operator is `Checked!int` 1478 Rhs = The right-hand side type involved in the operator 1479 1480 Returns: $(D mixin(x ~ "lhs")) for unary, $(D mixin("lhs" ~ x ~ "rhs")) for 1481 binary 1482 1483 */ 1484 typeof(~Lhs()) onOverflow(string x, Lhs)(ref Lhs lhs) 1485 { 1486 stderr.writefln("Overflow on unary operator: %s%s(%s)", 1487 x, Lhs.stringof, lhs); 1488 return mixin(x ~ "lhs"); 1489 } 1490 /// ditto 1491 typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) 1492 { 1493 stderr.writefln("Overflow on binary operator: %s(%s) %s %s(%s)", 1494 Lhs.stringof, lhs, x, Rhs.stringof, rhs); 1495 return mixin("lhs" ~ x ~ "rhs"); 1496 } 1497 } 1498 1499 /// 1500 unittest 1501 { 1502 auto x = checked!Warn(42); 1503 short x1 = cast(short) x; 1504 //x += long(int.max); 1505 auto y = checked!Warn(cast(const int) 42); 1506 short y1 = cast(const byte) y; 1507 } 1508 1509 // ProperCompare 1510 /** 1511 1512 Hook that provides arithmetically correct comparisons for equality and ordering. 1513 Comparing an object of type $(D Checked!(X, ProperCompare)) against another 1514 integral (for equality or ordering) ensures that no surprising conversions from 1515 signed to unsigned integral occur before the comparison. Using $(D Checked!(X, 1516 ProperCompare)) on either side of a comparison for equality against a 1517 floating-point number makes sure the integral can be properly converted to the 1518 floating point type, thus making sure equality is transitive. 1519 1520 */ 1521 struct ProperCompare 1522 { 1523 /** 1524 Hook for `==` and `!=` that ensures comparison against integral values has 1525 the behavior expected by the usual arithmetic rules. The built-in semantics 1526 yield surprising behavior when comparing signed values against unsigned 1527 values for equality, for example $(D uint.max == -1) or $(D -1_294_967_296 == 1528 3_000_000_000u). The call $(D hookOpEquals(x, y)) returns `true` if and only 1529 if `x` and `y` represent the same arithmetic number. 1530 1531 If one of the numbers is an integral and the other is a floating-point 1532 number, $(D hookOpEquals(x, y)) returns `true` if and only if the integral 1533 can be converted exactly (without approximation) to the floating-point 1534 number. This is in order to preserve transitivity of equality: if $(D 1535 hookOpEquals(x, y)) and $(D hookOpEquals(y, z)) then $(D hookOpEquals(y, 1536 z)), in case `x`, `y`, and `z` are a mix of integral and floating-point 1537 numbers. 1538 1539 Params: 1540 lhs = The left-hand side of the comparison for equality 1541 rhs = The right-hand side of the comparison for equality 1542 1543 Returns: 1544 The result of the comparison, `true` if the values are equal 1545 */ 1546 static bool hookOpEquals(L, R)(L lhs, R rhs) 1547 { 1548 alias C = typeof(lhs + rhs); 1549 static if (isFloatingPoint!C) 1550 { 1551 static if (!isFloatingPoint!L) 1552 { 1553 return hookOpEquals(rhs, lhs); 1554 } 1555 else static if (!isFloatingPoint!R) 1556 { 1557 static assert(isFloatingPoint!L && !isFloatingPoint!R); 1558 auto rhs1 = C(rhs); 1559 return lhs == rhs1 && cast(R) rhs1 == rhs; 1560 } 1561 else 1562 return lhs == rhs; 1563 } 1564 else 1565 { 1566 bool error; 1567 auto result = opChecked!"=="(lhs, rhs, error); 1568 if (error) 1569 { 1570 // Only possible error is a wrong "true" 1571 return false; 1572 } 1573 return result; 1574 } 1575 } 1576 1577 /** 1578 Hook for `<`, `<=`, `>`, and `>=` that ensures comparison against integral 1579 values has the behavior expected by the usual arithmetic rules. The built-in 1580 semantics yield surprising behavior when comparing signed values against 1581 unsigned values, for example $(D 0u < -1). The call $(D hookOpCmp(x, y)) 1582 returns `-1` if and only if `x` is smaller than `y` in abstract arithmetic 1583 sense. 1584 1585 If one of the numbers is an integral and the other is a floating-point 1586 number, $(D hookOpEquals(x, y)) returns a floating-point number that is `-1` 1587 if `x < y`, `0` if `x == y`, `1` if `x > y`, and `NaN` if the floating-point 1588 number is `NaN`. 1589 1590 Params: 1591 lhs = The left-hand side of the comparison for ordering 1592 rhs = The right-hand side of the comparison for ordering 1593 1594 Returns: 1595 The result of the comparison (negative if $(D lhs < rhs), positive if $(D 1596 lhs > rhs), `0` if the values are equal) 1597 */ 1598 static auto hookOpCmp(L, R)(L lhs, R rhs) 1599 { 1600 alias C = typeof(lhs + rhs); 1601 static if (isFloatingPoint!C) 1602 { 1603 return lhs < rhs 1604 ? C(-1) 1605 : lhs > rhs ? C(1) : lhs == rhs ? C(0) : C.init; 1606 } 1607 else 1608 { 1609 static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) 1610 { 1611 static assert(isUnsigned!C); 1612 static assert(isUnsigned!L != isUnsigned!R); 1613 if (!isUnsigned!L && lhs < 0) 1614 return -1; 1615 if (!isUnsigned!R && rhs < 0) 1616 return 1; 1617 } 1618 return lhs < rhs ? -1 : lhs > rhs; 1619 } 1620 } 1621 } 1622 1623 /// 1624 unittest 1625 { 1626 alias opEqualsProper = ProperCompare.hookOpEquals; 1627 assert(opEqualsProper(42, 42)); 1628 assert(opEqualsProper(42.0, 42.0)); 1629 assert(opEqualsProper(42u, 42)); 1630 assert(opEqualsProper(42, 42u)); 1631 assert(-1 == 4294967295u); 1632 assert(!opEqualsProper(-1, 4294967295u)); 1633 assert(!opEqualsProper(const uint(-1), -1)); 1634 assert(!opEqualsProper(uint(-1), -1.0)); 1635 assert(3_000_000_000U == -1_294_967_296); 1636 assert(!opEqualsProper(3_000_000_000U, -1_294_967_296)); 1637 } 1638 1639 unittest 1640 { 1641 alias opCmpProper = ProperCompare.hookOpCmp; 1642 assert(opCmpProper(42, 42) == 0); 1643 assert(opCmpProper(42, 42.0) == 0); 1644 assert(opCmpProper(41, 42.0) < 0); 1645 assert(opCmpProper(42, 41.0) > 0); 1646 assert(opCmpProper(41, double.init) is double.init); 1647 assert(opCmpProper(42u, 42) == 0); 1648 assert(opCmpProper(42, 42u) == 0); 1649 assert(opCmpProper(-1, uint(-1)) < 0); 1650 assert(opCmpProper(uint(-1), -1) > 0); 1651 assert(opCmpProper(-1.0, -1) == 0); 1652 } 1653 1654 unittest 1655 { 1656 auto x1 = Checked!(uint, ProperCompare)(42u); 1657 assert(x1.get < -1); 1658 assert(x1 > -1); 1659 } 1660 1661 // WithNaN 1662 /** 1663 1664 Hook that reserves a special value as a "Not a Number" representative. For 1665 signed integrals, the reserved value is `T.min`. For signed integrals, the 1666 reserved value is `T.max`. 1667 1668 The default value of a $(D Checked!(X, WithNaN)) is its NaN value, so care must 1669 be taken that all variables are explicitly initialized. Any arithmetic and logic 1670 operation involving at least on NaN becomes NaN itself. All of $(D a == b), $(D 1671 a < b), $(D a > b), $(D a <= b), $(D a >= b) yield `false` if at least one of 1672 `a` and `b` is NaN. 1673 1674 */ 1675 struct WithNaN 1676 { 1677 static: 1678 /** 1679 The default value used for values not explicitly initialized. It is the NaN 1680 value, i.e. `T.min` for signed integrals and `T.max` for unsigned integrals. 1681 */ 1682 enum T defaultValue(T) = T.min == 0 ? T.max : T.min; 1683 /** 1684 The maximum value representable is $(D T.max) for signed integrals, $(D 1685 T.max - 1) for unsigned integrals. The minimum value representable is $(D 1686 T.min + 1) for signed integrals, $(D 0) for unsigned integrals. 1687 */ 1688 enum T max(T) = cast(T) (T.min == 0 ? T.max - 1 : T.max); 1689 /// ditto 1690 enum T min(T) = cast(T) (T.min == 0 ? T(0) : T.min + 1); 1691 1692 /** 1693 If `rhs` is `WithNaN.defaultValue!Rhs`, returns 1694 `WithNaN.defaultValue!Lhs`. Otherwise, returns $(D cast(Lhs) rhs). 1695 1696 Params: 1697 rhs = the value being cast (`Rhs` is the first argument to `Checked`) 1698 Lhs = the target type of the cast 1699 1700 Returns: The result of the cast operation. 1701 */ 1702 Lhs hookOpCast(Lhs, Rhs)(Rhs rhs) 1703 { 1704 static if (is(Lhs == bool)) 1705 { 1706 return rhs != defaultValue!Rhs && rhs != 0; 1707 } 1708 else static if (valueConvertible!(Rhs, Lhs)) 1709 { 1710 return rhs != defaultValue!Rhs ? Lhs(rhs) : defaultValue!Lhs; 1711 } 1712 else 1713 { 1714 // Not value convertible, only viable option is rhs fits within the 1715 // bounds of Lhs 1716 static if (ProperCompare.hookOpCmp(Rhs.min, Lhs.min) < 0) 1717 { 1718 // Example: hookOpCast!short(int(42)), hookOpCast!uint(int(42)) 1719 if (ProperCompare.hookOpCmp(rhs, Lhs.min) < 0) 1720 return defaultValue!Lhs; 1721 } 1722 static if (ProperCompare.hookOpCmp(Rhs.max, Lhs.max) > 0) 1723 { 1724 // Example: hookOpCast!int(uint(42)) 1725 if (ProperCompare.hookOpCmp(rhs, Lhs.max) > 0) 1726 return defaultValue!Lhs; 1727 } 1728 return cast(Lhs) rhs; 1729 } 1730 } 1731 1732 /// 1733 unittest 1734 { 1735 auto x = checked!WithNaN(422); 1736 assert((cast(ubyte) x) == 255); 1737 x = checked!WithNaN(-422); 1738 assert((cast(byte) x) == -128); 1739 assert(cast(short) x == -422); 1740 assert(cast(bool) x); 1741 x = x.init; // set back to NaN 1742 assert(x != true); 1743 assert(x != false); 1744 } 1745 1746 /** 1747 1748 Returns `false` if $(D lhs == WithNaN.defaultValue!Lhs), $(D lhs == rhs) 1749 otherwise. 1750 1751 Params: 1752 lhs = The left-hand side of the comparison (`Lhs` is the first argument to 1753 `Checked`) 1754 rhs = The right-hand side of the comparison 1755 1756 Returns: `lhs != WithNaN.defaultValue!Lhs && lhs == rhs` 1757 */ 1758 bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) 1759 { 1760 return lhs != defaultValue!Lhs && lhs == rhs; 1761 } 1762 1763 /** 1764 1765 If $(D lhs == WithNaN.defaultValue!Lhs), returns `double.init`. Otherwise, 1766 has the same semantics as the default comparison. 1767 1768 Params: 1769 lhs = The left-hand side of the comparison (`Lhs` is the first argument to 1770 `Checked`) 1771 rhs = The right-hand side of the comparison 1772 1773 Returns: `double.init` if `lhs == WitnNaN.defaultValue!Lhs`, `-1.0` if $(D 1774 lhs < rhs), `0.0` if $(D lhs == rhs), `1.0` if $(D lhs > rhs). 1775 1776 */ 1777 double hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) 1778 { 1779 if (lhs == defaultValue!Lhs) return double.init; 1780 return lhs < rhs 1781 ? -1.0 1782 : lhs > rhs ? 1.0 : lhs == rhs ? 0.0 : double.init; 1783 } 1784 1785 /// 1786 unittest 1787 { 1788 Checked!(int, WithNaN) x; 1789 assert(!(x < 0) && !(x > 0) && !(x == 0)); 1790 x = 1; 1791 assert(x > 0 && !(x < 0) && !(x == 0)); 1792 } 1793 1794 /** 1795 Defines hooks for unary operators `-`, `~`, `++`, and `--`. 1796 1797 For `-` and `~`, if $(D v == WithNaN.defaultValue!T), returns 1798 `WithNaN.defaultValue!T`. Otherwise, the semantics is the same as for the 1799 built-in operator. 1800 1801 For `++` and `--`, if $(D v == WithNaN.defaultValue!Lhs) or the operation 1802 would result in an overflow, sets `v` to `WithNaN.defaultValue!T`. 1803 Otherwise, the semantics is the same as for the built-in operator. 1804 1805 Params: 1806 x = The operator symbol 1807 v = The left-hand side of the comparison (`T` is the first argument to 1808 `Checked`) 1809 1810 Returns: $(UL $(LI For $(D x == "-" || x == "~"): If $(D v == 1811 WithNaN.defaultValue!T), the function returns `WithNaN.defaultValue!T`. 1812 Otherwise it returns the normal result of the operator.) $(LI For $(D x == 1813 "++" || x == "--"): The function returns `void`.)) 1814 1815 */ 1816 auto hookOpUnary(string x, T)(ref T v) 1817 { 1818 static if (x == "-" || x == "~") 1819 { 1820 return v != defaultValue!T ? mixin(x ~ "v") : v; 1821 } 1822 else static if (x == "++") 1823 { 1824 static if (defaultValue!T == T.min) 1825 { 1826 if (v != defaultValue!T) 1827 { 1828 if (v == T.max) v = defaultValue!T; 1829 else ++v; 1830 } 1831 } 1832 else 1833 { 1834 static assert(defaultValue!T == T.max); 1835 if (v != defaultValue!T) ++v; 1836 } 1837 } 1838 else static if (x == "--") 1839 { 1840 if (v != defaultValue!T) --v; 1841 } 1842 } 1843 1844 /// 1845 unittest 1846 { 1847 Checked!(int, WithNaN) x; 1848 ++x; 1849 assert(x.isNaN); 1850 x = 1; 1851 assert(!x.isNaN); 1852 x = -x; 1853 ++x; 1854 assert(!x.isNaN); 1855 } 1856 1857 unittest // for coverage 1858 { 1859 Checked!(uint, WithNaN) y; 1860 ++y; 1861 assert(y.isNaN); 1862 } 1863 1864 /** 1865 Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, 1866 `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the 1867 left-hand side operand. If $(D lhs == WithNaN.defaultValue!Lhs), returns 1868 $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the 1869 operand. Otherwise, evaluates the operand. If evaluation does not overflow, 1870 returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs 1871 + rhs))). 1872 1873 Params: 1874 x = The operator symbol 1875 lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) 1876 rhs = The right-hand side operand 1877 1878 Returns: If $(D lhs != WithNaN.defaultValue!Lhs) and the operator does not 1879 overflow, the function returns the same result as the built-in operator. In 1880 all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). 1881 */ 1882 auto hookOpBinary(string x, L, R)(L lhs, R rhs) 1883 { 1884 alias Result = typeof(lhs + rhs); 1885 if (lhs != defaultValue!L) 1886 { 1887 bool error; 1888 auto result = opChecked!x(lhs, rhs, error); 1889 if (!error) return result; 1890 } 1891 return defaultValue!Result; 1892 } 1893 1894 /// 1895 unittest 1896 { 1897 Checked!(int, WithNaN) x; 1898 assert((x + 1).isNaN); 1899 x = 100; 1900 assert(!(x + 1).isNaN); 1901 } 1902 1903 /** 1904 Defines hooks for binary operators `+`, `-`, `*`, `/`, `%`, `^^`, `&`, `|`, 1905 `^`, `<<`, `>>`, and `>>>` for cases where a `Checked` object is the 1906 right-hand side operand. If $(D rhs == WithNaN.defaultValue!Rhs), returns 1907 $(D WithNaN.defaultValue!(typeof(lhs + rhs))) without evaluating the 1908 operand. Otherwise, evaluates the operand. If evaluation does not overflow, 1909 returns the result. Otherwise, returns $(D WithNaN.defaultValue!(typeof(lhs 1910 + rhs))). 1911 1912 Params: 1913 x = The operator symbol 1914 lhs = The left-hand side operand 1915 rhs = The right-hand side operand (`Rhs` is the first argument to `Checked`) 1916 1917 Returns: If $(D rhs != WithNaN.defaultValue!Rhs) and the operator does not 1918 overflow, the function returns the same result as the built-in operator. In 1919 all other cases, returns $(D WithNaN.defaultValue!(typeof(lhs + rhs))). 1920 */ 1921 auto hookOpBinaryRight(string x, L, R)(L lhs, R rhs) 1922 { 1923 alias Result = typeof(lhs + rhs); 1924 if (rhs != defaultValue!R) 1925 { 1926 bool error; 1927 auto result = opChecked!x(lhs, rhs, error); 1928 if (!error) return result; 1929 } 1930 return defaultValue!Result; 1931 } 1932 /// 1933 unittest 1934 { 1935 Checked!(int, WithNaN) x; 1936 assert((1 + x).isNaN); 1937 x = 100; 1938 assert(!(1 + x).isNaN); 1939 } 1940 1941 /** 1942 1943 Defines hooks for binary operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, 1944 `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=` for cases where a `Checked` 1945 object is the left-hand side operand. If $(D lhs == 1946 WithNaN.defaultValue!Lhs), no action is carried. Otherwise, evaluates the 1947 operand. If evaluation does not overflow and fits in `Lhs` without loss of 1948 information or change of sign, sets `lhs` to the result. Otherwise, sets 1949 `lhs` to `WithNaN.defaultValue!Lhs`. 1950 1951 Params: 1952 x = The operator symbol (without the `=`) 1953 lhs = The left-hand side operand (`Lhs` is the first argument to `Checked`) 1954 rhs = The right-hand side operand 1955 1956 Returns: `void` 1957 */ 1958 void hookOpOpAssign(string x, L, R)(ref L lhs, R rhs) 1959 { 1960 if (lhs == defaultValue!L) 1961 return; 1962 bool error; 1963 auto temp = opChecked!x(lhs, rhs, error); 1964 lhs = error 1965 ? defaultValue!L 1966 : hookOpCast!L(temp); 1967 } 1968 1969 /// 1970 unittest 1971 { 1972 Checked!(int, WithNaN) x; 1973 x += 4; 1974 assert(x.isNaN); 1975 x = 0; 1976 x += 4; 1977 assert(!x.isNaN); 1978 x += int.max; 1979 assert(x.isNaN); 1980 } 1981 } 1982 1983 /// 1984 unittest 1985 { 1986 auto x1 = Checked!(int, WithNaN)(); 1987 assert(x1.isNaN); 1988 assert(x1.get == int.min); 1989 assert(x1 != x1); 1990 assert(!(x1 < x1)); 1991 assert(!(x1 > x1)); 1992 assert(!(x1 == x1)); 1993 ++x1; 1994 assert(x1.isNaN); 1995 assert(x1.get == int.min); 1996 --x1; 1997 assert(x1.isNaN); 1998 assert(x1.get == int.min); 1999 x1 = 42; 2000 assert(!x1.isNaN); 2001 assert(x1 == x1); 2002 assert(x1 <= x1); 2003 assert(x1 >= x1); 2004 static assert(x1.min == int.min + 1); 2005 x1 += long(int.max); 2006 } 2007 2008 /** 2009 Queries whether a $(D Checked!(T, WithNaN)) object is not a number (NaN). 2010 2011 Params: x = the `Checked` instance queried 2012 2013 Returns: `true` if `x` is a NaN, `false` otherwise 2014 */ 2015 bool isNaN(T)(const Checked!(T, WithNaN) x) 2016 { 2017 return x.get == x.init.get; 2018 } 2019 2020 /// 2021 unittest 2022 { 2023 auto x1 = Checked!(int, WithNaN)(); 2024 assert(x1.isNaN); 2025 x1 = 1; 2026 assert(!x1.isNaN); 2027 x1 = x1.init; 2028 assert(x1.isNaN); 2029 } 2030 2031 unittest 2032 { 2033 void test1(T)() 2034 { 2035 auto x1 = Checked!(T, WithNaN)(); 2036 assert(x1.isNaN); 2037 assert(x1.get == int.min); 2038 assert(x1 != x1); 2039 assert(!(x1 < x1)); 2040 assert(!(x1 > x1)); 2041 assert(!(x1 == x1)); 2042 assert(x1.get == int.min); 2043 auto x2 = Checked!(T, WithNaN)(42); 2044 assert(!x2.isNaN); 2045 assert(x2 == x2); 2046 assert(x2 <= x2); 2047 assert(x2 >= x2); 2048 static assert(x2.min == T.min + 1); 2049 } 2050 test1!int; 2051 test1!(const int); 2052 test1!(immutable int); 2053 2054 void test2(T)() 2055 { 2056 auto x1 = Checked!(T, WithNaN)(); 2057 assert(x1.get == T.min); 2058 assert(x1 != x1); 2059 assert(!(x1 < x1)); 2060 assert(!(x1 > x1)); 2061 assert(!(x1 == x1)); 2062 ++x1; 2063 assert(x1.get == T.min); 2064 --x1; 2065 assert(x1.get == T.min); 2066 x1 = 42; 2067 assert(x1 == x1); 2068 assert(x1 <= x1); 2069 assert(x1 >= x1); 2070 static assert(x1.min == T.min + 1); 2071 x1 += long(T.max); 2072 } 2073 test2!int; 2074 } 2075 2076 unittest 2077 { 2078 alias Smart(T) = Checked!(Checked!(T, ProperCompare), WithNaN); 2079 Smart!int x1; 2080 assert(x1 != x1); 2081 x1 = -1; 2082 assert(x1 < 1u); 2083 auto x2 = Smart!(const int)(42); 2084 } 2085 2086 // Saturate 2087 /** 2088 2089 Hook that implements $(I saturation), i.e. any arithmetic operation that would 2090 overflow leaves the result at its extreme value (`min` or `max` depending on the 2091 direction of the overflow). 2092 2093 Saturation is not sticky; if a value reaches its saturation value, another 2094 operation may take it back to normal range. 2095 2096 */ 2097 struct Saturate 2098 { 2099 static: 2100 /** 2101 2102 Implements saturation for operators `+=`, `-=`, `*=`, `/=`, `%=`, `^^=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, 2103 and `>>>=`. This hook is called if the result of the binary operation does 2104 not fit in `Lhs` without loss of information or a change in sign. 2105 2106 Params: 2107 Rhs = The right-hand side type in the assignment, after the operation has 2108 been computed 2109 bound = The bound being violated 2110 2111 Returns: `Lhs.max` if $(D rhs >= 0), `Lhs.min` otherwise. 2112 2113 */ 2114 T onLowerBound(Rhs, T)(Rhs rhs, T bound) 2115 { 2116 return bound; 2117 } 2118 /// ditto 2119 T onUpperBound(Rhs, T)(Rhs rhs, T bound) 2120 { 2121 return bound; 2122 } 2123 /// 2124 unittest 2125 { 2126 auto x = checked!Saturate(short(100)); 2127 x += 33000; 2128 assert(x == short.max); 2129 x -= 70000; 2130 assert(x == short.min); 2131 } 2132 2133 /** 2134 2135 Implements saturation for operators `+`, `-` (unary and binary), `*`, `/`, 2136 `%`, `^^`, `&`, `|`, `^`, `<<`, `>>`, and `>>>`. 2137 2138 For unary `-`, `onOverflow` is called if $(D lhs == Lhs.min) and `Lhs` is a 2139 signed type. The function returns `Lhs.max`. 2140 2141 For binary operators, the result is as follows: $(UL $(LI `Lhs.max` if the 2142 result overflows in the positive direction, on division by `0`, or on 2143 shifting right by a negative value) $(LI `Lhs.min` if the result overflows 2144 in the negative direction) $(LI `0` if `lhs` is being shifted left by a 2145 negative value, or shifted right by a large positive value)) 2146 2147 Params: 2148 x = The operator involved in the `opAssign` operation 2149 Lhs = The left-hand side of the operator (`Lhs` is the first argument to 2150 `Checked`) 2151 Rhs = The right-hand side type in the operator 2152 2153 Returns: The saturated result of the operator. 2154 2155 */ 2156 typeof(~Lhs()) onOverflow(string x, Lhs)(Lhs lhs) 2157 { 2158 static assert(x == "-" || x == "++" || x == "--"); 2159 return x == "--" ? Lhs.min : Lhs.max; 2160 } 2161 /// ditto 2162 typeof(Lhs() + Rhs()) onOverflow(string x, Lhs, Rhs)(Lhs lhs, Rhs rhs) 2163 { 2164 static if (x == "+") 2165 return rhs >= 0 ? Lhs.max : Lhs.min; 2166 else static if (x == "*") 2167 return (lhs >= 0) == (rhs >= 0) ? Lhs.max : Lhs.min; 2168 else static if (x == "^^") 2169 return lhs > 0 || !(rhs & 1) ? Lhs.max : Lhs.min; 2170 else static if (x == "-") 2171 return rhs >= 0 ? Lhs.min : Lhs.max; 2172 else static if (x == "/" || x == "%") 2173 return Lhs.max; 2174 else static if (x == "<<") 2175 return rhs >= 0 ? Lhs.max : 0; 2176 else static if (x == ">>" || x == ">>>") 2177 return rhs >= 0 ? 0 : Lhs.max; 2178 else 2179 static assert(false); 2180 } 2181 /// 2182 unittest 2183 { 2184 assert(checked!Saturate(int.max) + 1 == int.max); 2185 assert(checked!Saturate(100) ^^ 10 == int.max); 2186 assert(checked!Saturate(-100) ^^ 10 == int.max); 2187 assert(checked!Saturate(100) / 0 == int.max); 2188 assert(checked!Saturate(100) << -1 == 0); 2189 assert(checked!Saturate(100) << 33 == int.max); 2190 assert(checked!Saturate(100) >> -1 == int.max); 2191 assert(checked!Saturate(100) >> 33 == 0); 2192 } 2193 } 2194 2195 /// 2196 unittest 2197 { 2198 auto x = checked!Saturate(int.max); 2199 ++x; 2200 assert(x == int.max); 2201 --x; 2202 assert(x == int.max - 1); 2203 x = int.min; 2204 assert(-x == int.max); 2205 x -= 42; 2206 assert(x == int.min); 2207 assert(x * -2 == int.max); 2208 } 2209 2210 /* 2211 Yields `true` if `T1` is "value convertible" (by C's "value preserving" rule, 2212 see $(HTTP c-faq.com/expr/preservingrules.html)) to `T2`, where the two are 2213 integral types. That is, all of values in `T1` are also in `T2`. For example 2214 `int` is value convertible to `long` but not to `uint` or `ulong`. 2215 */ 2216 private enum valueConvertible(T1, T2) = isIntegral!T1 && isIntegral!T2 && 2217 is(T1 : T2) && ( 2218 isUnsigned!T1 == isUnsigned!T2 || // same signedness 2219 !isUnsigned!T2 && T2.sizeof > T1.sizeof // safely convertible 2220 ); 2221 2222 /** 2223 2224 Defines binary operations with overflow checking for any two integral types. 2225 The result type obeys the language rules (even when they may be 2226 counterintuitive), and `overflow` is set if an overflow occurs (including 2227 inadvertent change of signedness, e.g. `-1` is converted to `uint`). 2228 Conceptually the behavior is: 2229 2230 $(OL $(LI Perform the operation in infinite precision) 2231 $(LI If the infinite-precision result fits in the result type, return it and 2232 do not touch `overflow`) 2233 $(LI Otherwise, set `overflow` to `true` and return an unspecified value) 2234 ) 2235 2236 The implementation exploits properties of types and operations to minimize 2237 additional work. 2238 2239 Params: 2240 x = The binary operator involved, e.g. `/` 2241 lhs = The left-hand side of the operator 2242 rhs = The right-hand side of the operator 2243 error = The error indicator (assigned `true` in case there's an error) 2244 2245 Returns: 2246 The result of the operation, which is the same as the built-in operator 2247 */ 2248 typeof(mixin(x == "cmp" ? "0" : ("L() " ~ x ~ " R()"))) 2249 opChecked(string x, L, R)(const L lhs, const R rhs, ref bool error) 2250 if (isIntegral!L && isIntegral!R) 2251 { 2252 static if (x == "cmp") 2253 alias Result = int; 2254 else 2255 alias Result = typeof(mixin("L() " ~ x ~ " R()")); 2256 2257 import core.checkedint; 2258 import std.algorithm.comparison : among; 2259 static if (x == "==") 2260 { 2261 alias C = typeof(lhs + rhs); 2262 static if (valueConvertible!(L, C) && valueConvertible!(R, C)) 2263 { 2264 // Values are converted to R before comparison, cool. 2265 return lhs == rhs; 2266 } 2267 else 2268 { 2269 static assert(isUnsigned!C); 2270 static assert(isUnsigned!L != isUnsigned!R); 2271 if (lhs != rhs) return false; 2272 // R(lhs) and R(rhs) have the same bit pattern, yet may be 2273 // different due to signedness change. 2274 static if (!isUnsigned!R) 2275 { 2276 if (rhs >= 0) 2277 return true; 2278 } 2279 else 2280 { 2281 if (lhs >= 0) 2282 return true; 2283 } 2284 error = true; 2285 return true; 2286 } 2287 } 2288 else static if (x == "cmp") 2289 { 2290 alias C = typeof(lhs + rhs); 2291 static if (!valueConvertible!(L, C) || !valueConvertible!(R, C)) 2292 { 2293 static assert(isUnsigned!C); 2294 static assert(isUnsigned!L != isUnsigned!R); 2295 if (!isUnsigned!L && lhs < 0) 2296 { 2297 error = true; 2298 return -1; 2299 } 2300 if (!isUnsigned!R && rhs < 0) 2301 { 2302 error = true; 2303 return 1; 2304 } 2305 } 2306 return lhs < rhs ? -1 : lhs > rhs; 2307 } 2308 else static if (x.among("<<", ">>", ">>>")) 2309 { 2310 // Handle shift separately from all others. The test below covers 2311 // negative rhs as well. 2312 import std.conv : unsigned; 2313 if (unsigned(rhs) > 8 * Result.sizeof) goto fail; 2314 return mixin("lhs" ~ x ~ "rhs"); 2315 } 2316 else static if (x.among("&", "|", "^")) 2317 { 2318 // Nothing to check 2319 return mixin("lhs" ~ x ~ "rhs"); 2320 } 2321 else static if (x == "^^") 2322 { 2323 // Exponentiation is weird, handle separately 2324 return pow(lhs, rhs, error); 2325 } 2326 else static if (valueConvertible!(L, Result) && 2327 valueConvertible!(R, Result)) 2328 { 2329 static if (L.sizeof < Result.sizeof && R.sizeof < Result.sizeof && 2330 x.among("+", "-", "*")) 2331 { 2332 // No checks - both are value converted and result is in range 2333 return mixin("lhs" ~ x ~ "rhs"); 2334 } 2335 else static if (x == "+") 2336 { 2337 static if (isUnsigned!Result) alias impl = addu; 2338 else alias impl = adds; 2339 return impl(Result(lhs), Result(rhs), error); 2340 } 2341 else static if (x == "-") 2342 { 2343 static if (isUnsigned!Result) alias impl = subu; 2344 else alias impl = subs; 2345 return impl(Result(lhs), Result(rhs), error); 2346 } 2347 else static if (x == "*") 2348 { 2349 static if (!isUnsigned!L && !isUnsigned!R && 2350 is(L == Result)) 2351 { 2352 if (lhs == Result.min && rhs == -1) goto fail; 2353 } 2354 static if (isUnsigned!Result) alias impl = mulu; 2355 else alias impl = muls; 2356 return impl(Result(lhs), Result(rhs), error); 2357 } 2358 else static if (x == "/" || x == "%") 2359 { 2360 static if (!isUnsigned!L && !isUnsigned!R && 2361 is(L == Result) && x == "/") 2362 { 2363 if (lhs == Result.min && rhs == -1) goto fail; 2364 } 2365 if (rhs == 0) goto fail; 2366 return mixin("lhs" ~ x ~ "rhs"); 2367 } 2368 else static assert(0, x); 2369 } 2370 else // Mixed signs 2371 { 2372 static assert(isUnsigned!Result); 2373 static assert(isUnsigned!L != isUnsigned!R); 2374 static if (x == "+") 2375 { 2376 static if (!isUnsigned!L) 2377 { 2378 if (lhs < 0) 2379 return subu(Result(rhs), Result(-lhs), error); 2380 } 2381 else static if (!isUnsigned!R) 2382 { 2383 if (rhs < 0) 2384 return subu(Result(lhs), Result(-rhs), error); 2385 } 2386 return addu(Result(lhs), Result(rhs), error); 2387 } 2388 else static if (x == "-") 2389 { 2390 static if (!isUnsigned!L) 2391 { 2392 if (lhs < 0) goto fail; 2393 } 2394 else static if (!isUnsigned!R) 2395 { 2396 if (rhs < 0) 2397 return addu(Result(lhs), Result(-rhs), error); 2398 } 2399 return subu(Result(lhs), Result(rhs), error); 2400 } 2401 else static if (x == "*") 2402 { 2403 static if (!isUnsigned!L) 2404 { 2405 if (lhs < 0) goto fail; 2406 } 2407 else static if (!isUnsigned!R) 2408 { 2409 if (rhs < 0) goto fail; 2410 } 2411 return mulu(Result(lhs), Result(rhs), error); 2412 } 2413 else static if (x == "/" || x == "%") 2414 { 2415 static if (!isUnsigned!L) 2416 { 2417 if (lhs < 0 || rhs == 0) goto fail; 2418 } 2419 else static if (!isUnsigned!R) 2420 { 2421 if (rhs <= 0) goto fail; 2422 } 2423 return mixin("Result(lhs)" ~ x ~ "Result(rhs)"); 2424 } 2425 else static assert(0, x); 2426 } 2427 debug assert(false); 2428 fail: 2429 error = true; 2430 return Result(0); 2431 } 2432 2433 /// 2434 unittest 2435 { 2436 bool overflow; 2437 assert(opChecked!"+"(const short(1), short(1), overflow) == 2 && !overflow); 2438 assert(opChecked!"+"(1, 1, overflow) == 2 && !overflow); 2439 assert(opChecked!"+"(1, 1u, overflow) == 2 && !overflow); 2440 assert(opChecked!"+"(-1, 1u, overflow) == 0 && !overflow); 2441 assert(opChecked!"+"(1u, -1, overflow) == 0 && !overflow); 2442 } 2443 2444 /// 2445 unittest 2446 { 2447 bool overflow; 2448 assert(opChecked!"-"(1, 1, overflow) == 0 && !overflow); 2449 assert(opChecked!"-"(1, 1u, overflow) == 0 && !overflow); 2450 assert(opChecked!"-"(1u, -1, overflow) == 2 && !overflow); 2451 assert(opChecked!"-"(-1, 1u, overflow) == 0 && overflow); 2452 } 2453 2454 unittest 2455 { 2456 bool overflow; 2457 assert(opChecked!"*"(2, 3, overflow) == 6 && !overflow); 2458 assert(opChecked!"*"(2, 3u, overflow) == 6 && !overflow); 2459 assert(opChecked!"*"(1u, -1, overflow) == 0 && overflow); 2460 //assert(mul(-1, 1u, overflow) == uint.max - 1 && overflow); 2461 } 2462 2463 unittest 2464 { 2465 bool overflow; 2466 assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); 2467 assert(opChecked!"/"(6, 3, overflow) == 2 && !overflow); 2468 assert(opChecked!"/"(6u, 3, overflow) == 2 && !overflow); 2469 assert(opChecked!"/"(6, 3u, overflow) == 2 && !overflow); 2470 assert(opChecked!"/"(11, 0, overflow) == 0 && overflow); 2471 overflow = false; 2472 assert(opChecked!"/"(6u, 0, overflow) == 0 && overflow); 2473 overflow = false; 2474 assert(opChecked!"/"(-6, 2u, overflow) == 0 && overflow); 2475 overflow = false; 2476 assert(opChecked!"/"(-6, 0u, overflow) == 0 && overflow); 2477 overflow = false; 2478 assert(opChecked!"cmp"(0u, -6, overflow) == 1 && overflow); 2479 overflow = false; 2480 assert(opChecked!"|"(1, 2, overflow) == 3 && !overflow); 2481 } 2482 2483 /* 2484 Exponentiation function used by the implementation of operator `^^`. 2485 */ 2486 private pure @safe nothrow @nogc 2487 auto pow(L, R)(const L lhs, const R rhs, ref bool overflow) 2488 if (isIntegral!L && isIntegral!R) 2489 { 2490 if (rhs <= 1) 2491 { 2492 if (rhs == 0) return 1; 2493 static if (!isUnsigned!R) 2494 return rhs == 1 2495 ? lhs 2496 : (rhs == -1 && (lhs == 1 || lhs == -1)) ? lhs : 0; 2497 else 2498 return lhs; 2499 } 2500 2501 typeof(lhs ^^ rhs) b = void; 2502 static if (!isUnsigned!L && isUnsigned!(typeof(b))) 2503 { 2504 // Need to worry about mixed-sign stuff 2505 if (lhs < 0) 2506 { 2507 if (rhs & 1) 2508 { 2509 if (lhs < 0) overflow = true; 2510 return 0; 2511 } 2512 b = -lhs; 2513 } 2514 else 2515 { 2516 b = lhs; 2517 } 2518 } 2519 else 2520 { 2521 b = lhs; 2522 } 2523 if (b == 1) return 1; 2524 if (b == -1) return (rhs & 1) ? -1 : 1; 2525 if (rhs > 63) 2526 { 2527 overflow = true; 2528 return 0; 2529 } 2530 2531 assert((b > 1 || b < -1) && rhs > 1); 2532 return powImpl(b, cast(uint) rhs, overflow); 2533 } 2534 2535 // Inspiration: http://www.stepanovpapers.com/PAM.pdf 2536 pure @safe nothrow @nogc 2537 private T powImpl(T)(T b, uint e, ref bool overflow) 2538 if (isIntegral!T && T.sizeof >= 4) 2539 { 2540 assert(e > 1); 2541 2542 import core.checkedint : muls, mulu; 2543 static if (isUnsigned!T) alias mul = mulu; 2544 else alias mul = muls; 2545 2546 T r = b; 2547 --e; 2548 // Loop invariant: r * (b ^^ e) is the actual result 2549 for (;; e /= 2) 2550 { 2551 if (e % 2) 2552 { 2553 r = mul(r, b, overflow); 2554 if (e == 1) break; 2555 } 2556 b = mul(b, b, overflow); 2557 } 2558 return r; 2559 } 2560 2561 unittest 2562 { 2563 static void testPow(T)(T x, uint e) 2564 { 2565 bool overflow; 2566 assert(opChecked!"^^"(T(0), 0, overflow) == 1); 2567 assert(opChecked!"^^"(-2, T(0), overflow) == 1); 2568 assert(opChecked!"^^"(-2, T(1), overflow) == -2); 2569 assert(opChecked!"^^"(-1, -1, overflow) == -1); 2570 assert(opChecked!"^^"(-2, 1, overflow) == -2); 2571 assert(opChecked!"^^"(-2, -1, overflow) == 0); 2572 assert(opChecked!"^^"(-2, 4u, overflow) == 16); 2573 assert(!overflow); 2574 assert(opChecked!"^^"(-2, 3u, overflow) == 0); 2575 assert(overflow); 2576 overflow = false; 2577 assert(opChecked!"^^"(3, 64u, overflow) == 0); 2578 assert(overflow); 2579 overflow = false; 2580 foreach (uint i; 0 .. e) 2581 { 2582 assert(opChecked!"^^"(x, i, overflow) == x ^^ i); 2583 assert(!overflow); 2584 } 2585 assert(opChecked!"^^"(x, e, overflow) == x ^^ e); 2586 assert(overflow); 2587 } 2588 2589 testPow!int(3, 21); 2590 testPow!uint(3, 21); 2591 testPow!long(3, 40); 2592 testPow!ulong(3, 41); 2593 } 2594 2595 version(unittest) private struct CountOverflows 2596 { 2597 uint calls; 2598 auto onOverflow(string op, Lhs)(Lhs lhs) 2599 { 2600 ++calls; 2601 return mixin(op ~ "lhs"); 2602 } 2603 auto onOverflow(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) 2604 { 2605 ++calls; 2606 return mixin("lhs" ~ op ~ "rhs"); 2607 } 2608 T onLowerBound(Rhs, T)(Rhs rhs, T bound) 2609 { 2610 ++calls; 2611 return cast(T) rhs; 2612 } 2613 T onUpperBound(Rhs, T)(Rhs rhs, T bound) 2614 { 2615 ++calls; 2616 return cast(T) rhs; 2617 } 2618 } 2619 2620 version(unittest) private struct CountOpBinary 2621 { 2622 uint calls; 2623 auto hookOpBinary(string op, Lhs, Rhs)(Lhs lhs, Rhs rhs) 2624 { 2625 ++calls; 2626 return mixin("lhs" ~ op ~ "rhs"); 2627 } 2628 } 2629 2630 // opBinary 2631 @nogc nothrow pure @safe unittest 2632 { 2633 auto x = Checked!(const int, void)(42), y = Checked!(immutable int, void)(142); 2634 assert(x + y == 184); 2635 assert(x + 100 == 142); 2636 assert(y - x == 100); 2637 assert(200 - x == 158); 2638 assert(y * x == 142 * 42); 2639 assert(x / 1 == 42); 2640 assert(x % 20 == 2); 2641 2642 auto x1 = Checked!(int, CountOverflows)(42); 2643 assert(x1 + 0 == 42); 2644 assert(x1 + false == 42); 2645 assert(is(typeof(x1 + 0.5) == double)); 2646 assert(x1 + 0.5 == 42.5); 2647 assert(x1.hook.calls == 0); 2648 assert(x1 + int.max == int.max + 42); 2649 assert(x1.hook.calls == 1); 2650 assert(x1 * 2 == 84); 2651 assert(x1.hook.calls == 1); 2652 assert(x1 / 2 == 21); 2653 assert(x1.hook.calls == 1); 2654 assert(x1 % 20 == 2); 2655 assert(x1.hook.calls == 1); 2656 assert(x1 << 2 == 42 << 2); 2657 assert(x1.hook.calls == 1); 2658 assert(x1 << 42 == x1.get << x1.get); 2659 assert(x1.hook.calls == 2); 2660 x1 = int.min; 2661 assert(x1 - 1 == int.max); 2662 assert(x1.hook.calls == 3); 2663 2664 auto x2 = Checked!(int, CountOpBinary)(42); 2665 assert(x2 + 1 == 43); 2666 assert(x2.hook.calls == 1); 2667 2668 auto x3 = Checked!(uint, CountOverflows)(42u); 2669 assert(x3 + 1 == 43); 2670 assert(x3.hook.calls == 0); 2671 assert(x3 - 1 == 41); 2672 assert(x3.hook.calls == 0); 2673 assert(x3 + (-42) == 0); 2674 assert(x3.hook.calls == 0); 2675 assert(x3 - (-42) == 84); 2676 assert(x3.hook.calls == 0); 2677 assert(x3 * 2 == 84); 2678 assert(x3.hook.calls == 0); 2679 assert(x3 * -2 == -84); 2680 assert(x3.hook.calls == 1); 2681 assert(x3 / 2 == 21); 2682 assert(x3.hook.calls == 1); 2683 assert(x3 / -2 == 0); 2684 assert(x3.hook.calls == 2); 2685 assert(x3 ^^ 2 == 42 * 42); 2686 assert(x3.hook.calls == 2); 2687 2688 auto x4 = Checked!(int, CountOverflows)(42); 2689 assert(x4 + 1 == 43); 2690 assert(x4.hook.calls == 0); 2691 assert(x4 + 1u == 43); 2692 assert(x4.hook.calls == 0); 2693 assert(x4 - 1 == 41); 2694 assert(x4.hook.calls == 0); 2695 assert(x4 * 2 == 84); 2696 assert(x4.hook.calls == 0); 2697 x4 = -2; 2698 assert(x4 + 2u == 0); 2699 assert(x4.hook.calls == 0); 2700 assert(x4 * 2u == -4); 2701 assert(x4.hook.calls == 1); 2702 2703 auto x5 = Checked!(int, CountOverflows)(3); 2704 assert(x5 ^^ 0 == 1); 2705 assert(x5 ^^ 1 == 3); 2706 assert(x5 ^^ 2 == 9); 2707 assert(x5 ^^ 3 == 27); 2708 assert(x5 ^^ 4 == 81); 2709 assert(x5 ^^ 5 == 81 * 3); 2710 assert(x5 ^^ 6 == 81 * 9); 2711 } 2712 2713 // opBinaryRight 2714 @nogc nothrow pure @safe unittest 2715 { 2716 auto x1 = Checked!(int, CountOverflows)(42); 2717 assert(1 + x1 == 43); 2718 assert(true + x1 == 43); 2719 assert(0.5 + x1 == 42.5); 2720 auto x2 = Checked!(int, void)(42); 2721 assert(x1 + x2 == 84); 2722 assert(x2 + x1 == 84); 2723 } 2724 2725 // opOpAssign 2726 unittest 2727 { 2728 auto x1 = Checked!(int, CountOverflows)(3); 2729 assert((x1 += 2) == 5); 2730 x1 *= 2_000_000_000L; 2731 assert(x1.hook.calls == 1); 2732 x1 *= -2_000_000_000L; 2733 assert(x1.hook.calls == 2); 2734 2735 auto x2 = Checked!(ushort, CountOverflows)(ushort(3)); 2736 assert((x2 += 2) == 5); 2737 assert(x2.hook.calls == 0); 2738 assert((x2 += ushort.max) == cast(ushort) (ushort(5) + ushort.max)); 2739 assert(x2.hook.calls == 1); 2740 2741 auto x3 = Checked!(uint, CountOverflows)(3u); 2742 x3 *= ulong(2_000_000_000); 2743 assert(x3.hook.calls == 1); 2744 } 2745 2746 // opAssign 2747 unittest 2748 { 2749 Checked!(int, void) x; 2750 x = 42; 2751 assert(x.get == 42); 2752 x = x; 2753 assert(x.get == 42); 2754 x = short(43); 2755 assert(x.get == 43); 2756 x = ushort(44); 2757 assert(x.get == 44); 2758 } 2759 2760 unittest 2761 { 2762 static assert(!is(typeof(Checked!(short, void)(ushort(42))))); 2763 static assert(!is(typeof(Checked!(int, void)(long(42))))); 2764 static assert(!is(typeof(Checked!(int, void)(ulong(42))))); 2765 assert(Checked!(short, void)(short(42)).get == 42); 2766 assert(Checked!(int, void)(ushort(42)).get == 42); 2767 } 2768 2769 // opCast 2770 @nogc nothrow pure @safe unittest 2771 { 2772 static assert(is(typeof(cast(float) Checked!(int, void)(42)) == float)); 2773 assert(cast(float) Checked!(int, void)(42) == 42); 2774 2775 assert(is(typeof(cast(long) Checked!(int, void)(42)) == long)); 2776 assert(cast(long) Checked!(int, void)(42) == 42); 2777 static assert(is(typeof(cast(long) Checked!(uint, void)(42u)) == long)); 2778 assert(cast(long) Checked!(uint, void)(42u) == 42); 2779 2780 auto x = Checked!(int, void)(42); 2781 if (x) {} else assert(0); 2782 x = 0; 2783 if (x) assert(0); 2784 2785 static struct Hook1 2786 { 2787 uint calls; 2788 Dst hookOpCast(Dst, Src)(Src value) 2789 { 2790 ++calls; 2791 return 42; 2792 } 2793 } 2794 auto y = Checked!(long, Hook1)(long.max); 2795 assert(cast(int) y == 42); 2796 assert(cast(uint) y == 42); 2797 assert(y.hook.calls == 2); 2798 2799 static struct Hook2 2800 { 2801 uint calls; 2802 Dst onBadCast(Dst, Src)(Src value) 2803 { 2804 ++calls; 2805 return 42; 2806 } 2807 } 2808 auto x1 = Checked!(uint, Hook2)(100u); 2809 assert(cast(ushort) x1 == 100); 2810 assert(cast(short) x1 == 100); 2811 assert(cast(float) x1 == 100); 2812 assert(cast(double) x1 == 100); 2813 assert(cast(real) x1 == 100); 2814 assert(x1.hook.calls == 0); 2815 assert(cast(int) x1 == 100); 2816 assert(x1.hook.calls == 0); 2817 x1 = uint.max; 2818 assert(cast(int) x1 == 42); 2819 assert(x1.hook.calls == 1); 2820 2821 auto x2 = Checked!(int, Hook2)(-100); 2822 assert(cast(short) x2 == -100); 2823 assert(cast(ushort) x2 == 42); 2824 assert(cast(uint) x2 == 42); 2825 assert(cast(ulong) x2 == 42); 2826 assert(x2.hook.calls == 3); 2827 } 2828 2829 // opEquals 2830 @nogc nothrow pure @safe unittest 2831 { 2832 assert(Checked!(int, void)(42) == 42L); 2833 assert(42UL == Checked!(int, void)(42)); 2834 2835 static struct Hook1 2836 { 2837 uint calls; 2838 bool hookOpEquals(Lhs, Rhs)(const Lhs lhs, const Rhs rhs) 2839 { 2840 ++calls; 2841 return lhs != rhs; 2842 } 2843 } 2844 auto x1 = Checked!(int, Hook1)(100); 2845 assert(x1 != Checked!(long, Hook1)(100)); 2846 assert(x1.hook.calls == 1); 2847 assert(x1 != 100u); 2848 assert(x1.hook.calls == 2); 2849 2850 static struct Hook2 2851 { 2852 uint calls; 2853 bool hookOpEquals(Lhs, Rhs)(Lhs lhs, Rhs rhs) 2854 { 2855 ++calls; 2856 return false; 2857 } 2858 } 2859 auto x2 = Checked!(int, Hook2)(-100); 2860 assert(x2 != x1); 2861 // For coverage: lhs has no hookOpEquals, rhs does 2862 assert(Checked!(uint, void)(100u) != x2); 2863 // For coverage: different types, neither has a hookOpEquals 2864 assert(Checked!(uint, void)(100u) == Checked!(int, void*)(100)); 2865 assert(x2.hook.calls == 0); 2866 assert(x2 != -100); 2867 assert(x2.hook.calls == 1); 2868 assert(x2 != cast(uint) -100); 2869 assert(x2.hook.calls == 2); 2870 x2 = 100; 2871 assert(x2 != cast(uint) 100); 2872 assert(x2.hook.calls == 3); 2873 x2 = -100; 2874 2875 auto x3 = Checked!(uint, Hook2)(100u); 2876 assert(x3 != 100); 2877 x3 = uint.max; 2878 assert(x3 != -1); 2879 2880 assert(x2 != x3); 2881 } 2882 2883 // opCmp 2884 @nogc nothrow pure @safe unittest 2885 { 2886 Checked!(int, void) x; 2887 assert(x <= x); 2888 assert(x < 45); 2889 assert(x < 45u); 2890 assert(x > -45); 2891 assert(x < 44.2); 2892 assert(x > -44.2); 2893 assert(!(x < double.init)); 2894 assert(!(x > double.init)); 2895 assert(!(x <= double.init)); 2896 assert(!(x >= double.init)); 2897 2898 static struct Hook1 2899 { 2900 uint calls; 2901 int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) 2902 { 2903 ++calls; 2904 return 0; 2905 } 2906 } 2907 auto x1 = Checked!(int, Hook1)(42); 2908 assert(!(x1 < 43u)); 2909 assert(!(43u < x1)); 2910 assert(x1.hook.calls == 2); 2911 2912 static struct Hook2 2913 { 2914 uint calls; 2915 int hookOpCmp(Lhs, Rhs)(Lhs lhs, Rhs rhs) 2916 { 2917 ++calls; 2918 return ProperCompare.hookOpCmp(lhs, rhs); 2919 } 2920 } 2921 auto x2 = Checked!(int, Hook2)(-42); 2922 assert(x2 < 43u); 2923 assert(43u > x2); 2924 assert(x2.hook.calls == 2); 2925 x2 = 42; 2926 assert(x2 > 41u); 2927 2928 auto x3 = Checked!(uint, Hook2)(42u); 2929 assert(x3 > 41); 2930 assert(x3 > -41); 2931 } 2932 2933 // opUnary 2934 @nogc nothrow pure @safe unittest 2935 { 2936 auto x = Checked!(int, void)(42); 2937 assert(x == +x); 2938 static assert(is(typeof(-x) == typeof(x))); 2939 assert(-x == Checked!(int, void)(-42)); 2940 static assert(is(typeof(~x) == typeof(x))); 2941 assert(~x == Checked!(int, void)(~42)); 2942 assert(++x == 43); 2943 assert(--x == 42); 2944 2945 static struct Hook1 2946 { 2947 uint calls; 2948 auto hookOpUnary(string op, T)(T value) if (op == "-") 2949 { 2950 ++calls; 2951 return T(42); 2952 } 2953 auto hookOpUnary(string op, T)(T value) if (op == "~") 2954 { 2955 ++calls; 2956 return T(43); 2957 } 2958 } 2959 auto x1 = Checked!(int, Hook1)(100); 2960 assert(is(typeof(-x1) == typeof(x1))); 2961 assert(-x1 == Checked!(int, Hook1)(42)); 2962 assert(is(typeof(~x1) == typeof(x1))); 2963 assert(~x1 == Checked!(int, Hook1)(43)); 2964 assert(x1.hook.calls == 2); 2965 2966 static struct Hook2 2967 { 2968 uint calls; 2969 auto hookOpUnary(string op, T)(ref T value) if (op == "++") 2970 { 2971 ++calls; 2972 --value; 2973 } 2974 auto hookOpUnary(string op, T)(ref T value) if (op == "--") 2975 { 2976 ++calls; 2977 ++value; 2978 } 2979 } 2980 auto x2 = Checked!(int, Hook2)(100); 2981 assert(++x2 == 99); 2982 assert(x2 == 99); 2983 assert(--x2 == 100); 2984 assert(x2 == 100); 2985 2986 auto x3 = Checked!(int, CountOverflows)(int.max - 1); 2987 assert(++x3 == int.max); 2988 assert(x3.hook.calls == 0); 2989 assert(++x3 == int.min); 2990 assert(x3.hook.calls == 1); 2991 assert(-x3 == int.min); 2992 assert(x3.hook.calls == 2); 2993 2994 x3 = int.min + 1; 2995 assert(--x3 == int.min); 2996 assert(x3.hook.calls == 2); 2997 assert(--x3 == int.max); 2998 assert(x3.hook.calls == 3); 2999 } 3000 3001 // 3002 @nogc nothrow pure @safe unittest 3003 { 3004 Checked!(int, void) x; 3005 assert(x == x); 3006 assert(x == +x); 3007 assert(x == -x); 3008 ++x; 3009 assert(x == 1); 3010 x++; 3011 assert(x == 2); 3012 3013 x = 42; 3014 assert(x == 42); 3015 const short _short = 43; 3016 x = _short; 3017 assert(x == _short); 3018 ushort _ushort = 44; 3019 x = _ushort; 3020 assert(x == _ushort); 3021 assert(x == 44.0); 3022 assert(x != 44.1); 3023 assert(x < 45); 3024 assert(x < 44.2); 3025 assert(x > -45); 3026 assert(x > -44.2); 3027 3028 assert(cast(long) x == 44); 3029 assert(cast(short) x == 44); 3030 3031 const Checked!(uint, void) y; 3032 assert(y <= y); 3033 assert(y == 0); 3034 assert(y < x); 3035 x = -1; 3036 assert(x > y); 3037 }